Urban Store’s authentication system is built from scratch on top of PyJWT and bcrypt — it deliberately avoids DRF SimpleJWT and Django’s built-in session framework. Every API view setsDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ALEJ4NDRO2025/urban-store/llms.txt
Use this file to discover all available pages before exploring further.
authentication_classes = [] to prevent DRF from intercepting tokens, and all identity checks are performed by decoding the JWT manually. New accounts must verify their email address with a 6-digit code before they can log in, and the verification step enforces rate limits to prevent brute-force attempts.
How JWT Works in Urban Store
Urban Store uses PyJWT directly. Tokens are signed withsettings.SECRET_KEY using the HS256 algorithm and carry a custom payload:
| Claim | Value |
|---|---|
user_id | MongoDB ObjectId as a string |
email | User’s email address (also used as the internal user identifier across services) |
is_admin | Boolean — true for admin accounts, false otherwise |
exp | Expiry timestamp — 7 days from issuance |
Token Usage
Pass the token as a Bearer token in every authenticated request:localStorage under the key access and reads it using localStorage.getItem('access').
Registration and Verification Flow
Register — POST /api/users/register/
The client posts Response
email, password, first_name, and last_name. The backend creates a new User document with is_verified=False, generates a random 6-digit code, stores it in verification_token (expires in 24 hours), and sends an HTML verification email.201:Verify Code — POST /api/users/verify-code/
The user submits their email and the 6-digit code from the email. The backend enforces a maximum of 3 failed attempts. After 3 failures, the account enters a 15-minute lockout. A correct code sets Response Response
is_verified=True and clears all verification fields.200:429 (too many attempts):Users who have not verified their email receive
403 Forbidden on login with the message "Debes verificar tu correo antes de iniciar sesión". Deactivated accounts (is_active=False) also receive 403.Resending the Verification Code
If the user loses or doesn’t receive the code they can request a new one:last_verification_sent_at. Requests within the cooldown window receive a 429 response with the number of seconds remaining. A successful resend generates a new 6-digit code, resets verification_attempts to 0, and extends the expiry by 24 hours.
Password Hashing
All passwords are hashed with bcrypt before storage:gensalt() call generates a new random salt for every hash, so identical passwords produce different hashes.
Admin Access
Theis_admin field on the User document is a boolean set to False by default. There is no API endpoint to promote a user to admin — the flag must be set directly in MongoDB (e.g. via MongoDB Atlas or a mongo shell command).
IsAdminMongo permission class both read is_admin from the JWT — not from the database — so a re-login is required after promoting a user.
Route Protection — Next.js Middleware
Themiddleware.js file at the project root intercepts all page navigations and enforces three rules:
Public Paths
Accessible without any token:
/, /catalog, /login, /register, /verify-email, /forgot-password, /reset-passwordProtected Paths
Any path not in the public list requires a valid
access cookie. Unauthenticated requests are redirected to /login?redirect=<original-path>.Admin-Only Paths
Any path starting with
/admin requires the is_admin claim in the JWT to be true. Non-admins are redirected to /.access cookie (not localStorage). The frontend must set this cookie in addition to storing the token in localStorage so that the middleware can access it server-side.
User Model Fields
TheUser document is stored in the users MongoDB collection. All fields come from users/models.py:
| Field | MongoEngine Type | Description |
|---|---|---|
email | EmailField(required=True, unique=True) | Primary identifier |
password | StringField(required=True) | bcrypt hash |
first_name | StringField(max_length=30) | Given name |
last_name | StringField(max_length=30) | Family name |
is_active | BooleanField(default=True) | Soft-delete flag |
is_admin | BooleanField(default=False) | Admin privilege flag |
is_verified | BooleanField(default=False) | Email verification status |
verification_token | StringField | Current 6-digit code |
verification_token_expires | DateTimeField | Code expiry (24 h for registration, 1 h for password reset) |
created_at | DateTimeField(default=datetime.utcnow) | Account creation timestamp |
last_verification_sent_at | DateTimeField | Timestamp of last code send (for cooldown enforcement) |
verification_attempts | IntField(default=0) | Failed code attempt counter |
last_failed_attempt_at | DateTimeField | Timestamp of last failed attempt (for lockout window) |
Soft Delete
Deleting a user account does not remove the document from MongoDB.DELETE /api/users/profile/ sets is_active=False:
403 Forbidden). They remain in the database for order history and audit purposes, and can be reactivated by setting is_active=True directly in MongoDB.
Additional Auth Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /api/users/profile/ | Get authenticated user’s profile |
PUT | /api/users/profile/ | Update first_name and last_name |
DELETE | /api/users/profile/ | Soft-delete account |
POST | /api/users/change-password/ | Change password (requires current password) |
POST | /api/users/forgot-password/ | Request a password-reset code via email |
POST | /api/users/reset-password/ | Confirm reset code and set new password |