# π Django Trusted Device
A plug-and-play Django app that adds **trusted device management** to your API authentication system using
`djangorestframework-simplejwt`. Automatically associates tokens with user devices, tracks login locations,
and enables per-device control over access and session management.
---
[](https://ganiyevuz.github.io/django-trusted-devices/)
## π Features
* π **JWT tokens** include a unique `device_uid`
* π **Auto-detect IP, region, and city** via [ipapi.co](https://ipapi.co)
* π‘οΈ **Per-device session tracking** with update/delete restrictions
* π **Custom** `TokenObtainPair`, `TokenRefresh`, and `TokenVerify` views
* πͺ **Logout unwanted sessions** from the device list
* π§Ό **Automatic cleanup**, optional global control rules
* π§© **API-ready** β supports DRF out of the box
* βοΈ **Fully customizable** via `TRUSTED_DEVICE` Django settings
* π« **Rejects refresh/verify** from unknown or expired devices
---
## π¦ Installation
```bash
pip install django-trusted-device
```
Add to your `INSTALLED_APPS`:
```python
INSTALLED_APPS = [
...
'trusted_devices',
'rest_framework_simplejwt.token_blacklist',
]
```
Run migrations:
```bash
python manage.py migrate
```
---
## βοΈ Configuration
Customize behavior in `settings.py`:
```python
TRUSTED_DEVICE = {
"DELETE_DELAY_MINUTES": 60 * 24 * 7, # 7 days
"UPDATE_DELAY_MINUTES": 60, # 1 hour
"ALLOW_GLOBAL_DELETE": True,
"ALLOW_GLOBAL_UPDATE": True,
}
```
---
## π§© Usage
## π SimpleJWT configuration
Replace default SimpleJWT serializers with TrustedDevice serializers.:
```python
from datetime import timedelta
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'trusted_devices.authentication.TrustedDeviceAuthentication',
),
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
"REFRESH_TOKEN_LIFETIME": timedelta(days=30),
"AUTH_HEADER_TYPES": ("Bearer",),
"TOKEN_OBTAIN_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenObtainPairSerializer',
"TOKEN_REFRESH_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenRefreshSerializer',
"TOKEN_VERIFY_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenVerifySerializer',
}
```
### π Custom Token Views
Replace the default SimpleJWT views with:
```python
from trusted_devices.views import (
TrustedDeviceTokenObtainPairView,
TrustedDeviceTokenRefreshView,
TrustedDeviceTokenVerifyView,
)
urlpatterns = [
path('api/token/', TrustedDeviceTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TrustedDeviceTokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TrustedDeviceTokenVerifyView.as_view(), name='token_verify'),
]
```
---
### π‘ Device Management API
Use the provided `TrustedDeviceViewSet`:
```python
from trusted_devices.views import TrustedDeviceViewSet
router.register(r'trusted-devices', TrustedDeviceViewSet, basename='trusted-device')
```
Endpoints:
* `GET /trusted-devices` β List all trusted devices
* `DELETE /trusted-devices/{device_uid}` β Delete a device
* `PATCH /trusted-devices/{device_uid}` β Update device permissions
---
## π€ Device Model
Each trusted device includes:
* `device_uid`: Unique UUID
* `user_agent`: Browser or device string
* `ip_address`: IP address
* `country`, `region`, `city`: Geolocation (via `ipapi.co`)
* `last_seen`, `created_at`: Timestamps
* `can_delete_other_devices`, `can_update_other_devices`: Optional privileges
---
## π§ How It Works
1. During login, a `device_uid` is generated and embedded in the token.
2. Clients use that token (with `device_uid`) for refresh/verify.
3. Each request is linked to a known device.
4. Users can manage or restrict their devices via API or Admin.
---
## π§ͺ Testing Locally
```bash
# π§© Create and activate a uv-managed virtual environment
uv venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# π¦ Install the package in editable mode with dev extras
uv pip install -e ".[dev]"
# π§ͺ Run the test suite
pytest
```
---
## π§± Dependencies
* Django
* Django REST Framework
* djangorestframework-simplejwt
* [ipapi.co](https://ipapi.co) (for IP geolocation)
---
## ποΈ Model Snapshot
| Field | Purpose |
| -------------------------- | ------------------- |
| `device_uid` | UUID primary key |
| `user_agent`, `ip_address` | Device fingerprint |
| `country / region / city` | Geoβlookup |
| `last_seen / created_at` | Activity timestamps |
| `can_update_other_devices` | Granular permission |
| `can_delete_other_devices` | Granular permission |
---
## π€ Collaboration & Contributing
We love community contributions! To collaborate:
1. **Fork** the repo and create a feature branch:
```bash
git checkout -b feature/my-amazing-idea
```
2. **Follow code style** β run:
```bash
make lint # runs flake8, isort, black
```
3. **Write & run tests**:
```bash
pytest
```
4. **Commit** with clear messages and open a **Pull Request**.
GitHub Actions will lint + test your branch automatically.
---
### π£οΈ Discussions & Issues
* π‘ Questions / ideas β [GitHub Discussions](https://github.com/ganiyevuz/django-trusted-devices/discussions)
* π Bugs / feature requests β [GitHub Issues](https://github.com/ganiyevuz/django-trusted-devices/issues)
---
### π Maintainer Workflow
* PRs require at least one approval and passing CI
* We **squashβmerge** to keep history clean
* Follows **Semantic Versioning** (`MAJOR.MINOR.PATCH`), tagged as `vX.Y.Z`
---
## π License
[MIT](LICENSE)
---
Made with β€οΈ by [Jahongir Ganiev](https://github.com/ganiyevuz)
Security questions or commercial support? Open an issue or email **[contact@jakhongir.dev](mailto:contact@jakhongir.dev)**
Raw data
{
"_id": null,
"home_page": null,
"name": "django-trusted-devices",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "django, trusted devices, user security, sessions, login, djangorestframework",
"author": null,
"author_email": "Jakhongir Ganiev <contact@jakhongir.dev>",
"download_url": "https://files.pythonhosted.org/packages/3e/91/10316b1d2b211d59f20eab39ae577ab71a38a0821505383ddca0b16b646f/django_trusted_devices-1.3.tar.gz",
"platform": null,
"description": "# \ud83d\udd10 Django Trusted Device\n\nA plug-and-play Django app that adds **trusted device management** to your API authentication system using\n`djangorestframework-simplejwt`. Automatically associates tokens with user devices, tracks login locations,\nand enables per-device control over access and session management.\n\n---\n[](https://ganiyevuz.github.io/django-trusted-devices/)\n\n\n## \ud83d\ude80 Features\n\n* \ud83d\udd11 **JWT tokens** include a unique `device_uid`\n* \ud83c\udf0d **Auto-detect IP, region, and city** via [ipapi.co](https://ipapi.co)\n* \ud83d\udee1\ufe0f **Per-device session tracking** with update/delete restrictions\n* \ud83d\udd04 **Custom** `TokenObtainPair`, `TokenRefresh`, and `TokenVerify` views\n* \ud83d\udeaa **Logout unwanted sessions** from the device list\n* \ud83e\uddfc **Automatic cleanup**, optional global control rules\n* \ud83e\udde9 **API-ready** \u2013 supports DRF out of the box\n* \u2699\ufe0f **Fully customizable** via `TRUSTED_DEVICE` Django settings\n* \ud83d\udeab **Rejects refresh/verify** from unknown or expired devices\n\n---\n\n## \ud83d\udce6 Installation\n\n```bash\npip install django-trusted-device\n```\n\nAdd to your `INSTALLED_APPS`:\n\n```python\nINSTALLED_APPS = [\n ...\n 'trusted_devices',\n 'rest_framework_simplejwt.token_blacklist',\n]\n```\n\nRun migrations:\n\n```bash\npython manage.py migrate\n```\n\n---\n\n## \u2699\ufe0f Configuration\n\nCustomize behavior in `settings.py`:\n\n```python\nTRUSTED_DEVICE = {\n \"DELETE_DELAY_MINUTES\": 60 * 24 * 7, # 7 days\n \"UPDATE_DELAY_MINUTES\": 60, # 1 hour\n \"ALLOW_GLOBAL_DELETE\": True,\n \"ALLOW_GLOBAL_UPDATE\": True,\n}\n```\n\n---\n\n## \ud83e\udde9 Usage\n\n\n## \ud83d\udd10 SimpleJWT configuration\n\nReplace default SimpleJWT serializers with TrustedDevice serializers.:\n\n```python\nfrom datetime import timedelta\n\nREST_FRAMEWORK = {\n 'DEFAULT_AUTHENTICATION_CLASSES': (\n 'trusted_devices.authentication.TrustedDeviceAuthentication',\n ),\n}\n\nSIMPLE_JWT = {\n \"ACCESS_TOKEN_LIFETIME\": timedelta(minutes=60),\n \"REFRESH_TOKEN_LIFETIME\": timedelta(days=30),\n \"AUTH_HEADER_TYPES\": (\"Bearer\",),\n \"TOKEN_OBTAIN_SERIALIZER\": 'trusted_devices.serializers.TrustedDeviceTokenObtainPairSerializer',\n \"TOKEN_REFRESH_SERIALIZER\": 'trusted_devices.serializers.TrustedDeviceTokenRefreshSerializer',\n \"TOKEN_VERIFY_SERIALIZER\": 'trusted_devices.serializers.TrustedDeviceTokenVerifySerializer',\n}\n\n```\n\n### \ud83d\udd10 Custom Token Views\n\nReplace the default SimpleJWT views with:\n\n```python\nfrom trusted_devices.views import (\n TrustedDeviceTokenObtainPairView,\n TrustedDeviceTokenRefreshView,\n TrustedDeviceTokenVerifyView,\n)\n\nurlpatterns = [\n path('api/token/', TrustedDeviceTokenObtainPairView.as_view(), name='token_obtain_pair'),\n path('api/token/refresh/', TrustedDeviceTokenRefreshView.as_view(), name='token_refresh'),\n path('api/token/verify/', TrustedDeviceTokenVerifyView.as_view(), name='token_verify'),\n]\n```\n\n---\n\n### \ud83d\udce1 Device Management API\n\nUse the provided `TrustedDeviceViewSet`:\n\n```python\nfrom trusted_devices.views import TrustedDeviceViewSet\n\nrouter.register(r'trusted-devices', TrustedDeviceViewSet, basename='trusted-device')\n```\n\nEndpoints:\n\n* `GET /trusted-devices` \u2014 List all trusted devices\n* `DELETE /trusted-devices/{device_uid}` \u2014 Delete a device\n* `PATCH /trusted-devices/{device_uid}` \u2014 Update device permissions\n\n---\n\n## \ud83d\udc64 Device Model\n\nEach trusted device includes:\n\n* `device_uid`: Unique UUID\n* `user_agent`: Browser or device string\n* `ip_address`: IP address\n* `country`, `region`, `city`: Geolocation (via `ipapi.co`)\n* `last_seen`, `created_at`: Timestamps\n* `can_delete_other_devices`, `can_update_other_devices`: Optional privileges\n\n---\n\n## \ud83e\udde0 How It Works\n\n1. During login, a `device_uid` is generated and embedded in the token.\n2. Clients use that token (with `device_uid`) for refresh/verify.\n3. Each request is linked to a known device.\n4. Users can manage or restrict their devices via API or Admin.\n\n---\n\n## \ud83e\uddea Testing Locally\n\n```bash\n# \ud83e\udde9 Create and activate a uv-managed virtual environment\nuv venv\nsource .venv/bin/activate # Windows: .venv\\Scripts\\activate\n\n# \ud83d\udce6 Install the package in editable mode with dev extras\nuv pip install -e \".[dev]\"\n\n# \ud83e\uddea Run the test suite\npytest\n```\n\n---\n\n## \ud83e\uddf1 Dependencies\n\n* Django\n* Django REST Framework\n* djangorestframework-simplejwt\n* [ipapi.co](https://ipapi.co) (for IP geolocation)\n\n---\n\n## \ud83d\uddc3\ufe0f Model Snapshot\n\n| Field | Purpose |\n| -------------------------- | ------------------- |\n| `device_uid` | UUID primary key |\n| `user_agent`, `ip_address` | Device fingerprint |\n| `country / region / city` | Geo\u2011lookup |\n| `last_seen / created_at` | Activity timestamps |\n| `can_update_other_devices` | Granular permission |\n| `can_delete_other_devices` | Granular permission |\n\n---\n\n## \ud83e\udd1d Collaboration & Contributing\n\nWe love community contributions! To collaborate:\n\n1. **Fork** the repo and create a feature branch:\n\n ```bash\n git checkout -b feature/my-amazing-idea\n ```\n\n2. **Follow code style** \u2013 run:\n\n ```bash\n make lint # runs flake8, isort, black\n ```\n\n3. **Write & run tests**:\n\n ```bash\n pytest\n ```\n\n4. **Commit** with clear messages and open a **Pull Request**.\n GitHub Actions will lint + test your branch automatically.\n\n---\n\n### \ud83d\udde3\ufe0f Discussions & Issues\n\n* \ud83d\udca1 Questions / ideas \u2192 [GitHub Discussions](https://github.com/ganiyevuz/django-trusted-devices/discussions)\n* \ud83d\udc1b Bugs / feature requests \u2192 [GitHub Issues](https://github.com/ganiyevuz/django-trusted-devices/issues)\n\n---\n\n### \ud83d\udee0 Maintainer Workflow\n\n* PRs require at least one approval and passing CI\n* We **squash\u2011merge** to keep history clean\n* Follows **Semantic Versioning** (`MAJOR.MINOR.PATCH`), tagged as `vX.Y.Z`\n\n---\n\n## \ud83d\udcc4 License\n\n[MIT](LICENSE)\n\n---\n\nMade with \u2764\ufe0f by [Jahongir Ganiev](https://github.com/ganiyevuz)\nSecurity questions or commercial support? Open an issue or email **[contact@jakhongir.dev](mailto:contact@jakhongir.dev)**\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Secure and manage trusted login devices for Django users",
"version": "1.3",
"project_urls": null,
"split_keywords": [
"django",
" trusted devices",
" user security",
" sessions",
" login",
" djangorestframework"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "a82cd8f20d3d64c7309b03fa87ed2e3a08acb73de85e52a8586742a5eec24208",
"md5": "5c507f78f124f8ba3fcdcb5fc48037f4",
"sha256": "5f9578f4c816c7bb3f44e6a1c6b7e45de5117b7d53cdafa6bfbcad500aeee235"
},
"downloads": -1,
"filename": "django_trusted_devices-1.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "5c507f78f124f8ba3fcdcb5fc48037f4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 16752,
"upload_time": "2025-08-14T11:23:59",
"upload_time_iso_8601": "2025-08-14T11:23:59.438958Z",
"url": "https://files.pythonhosted.org/packages/a8/2c/d8f20d3d64c7309b03fa87ed2e3a08acb73de85e52a8586742a5eec24208/django_trusted_devices-1.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "3e9110316b1d2b211d59f20eab39ae577ab71a38a0821505383ddca0b16b646f",
"md5": "3dabc9e4059a4d258017ab3e166b2c0e",
"sha256": "c3bb0f0c33fd1617f8e124d46a89bffdf04877bf40fe2916890b26aa9ba4d24e"
},
"downloads": -1,
"filename": "django_trusted_devices-1.3.tar.gz",
"has_sig": false,
"md5_digest": "3dabc9e4059a4d258017ab3e166b2c0e",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 12762,
"upload_time": "2025-08-14T11:24:00",
"upload_time_iso_8601": "2025-08-14T11:24:00.233543Z",
"url": "https://files.pythonhosted.org/packages/3e/91/10316b1d2b211d59f20eab39ae577ab71a38a0821505383ddca0b16b646f/django_trusted_devices-1.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-14 11:24:00",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "django-trusted-devices"
}