-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Auth
Design Overview
Introduction
This document outlines the authentication mechanism for our website (frontend.website.com). The system utilizes the university's official questionnaire platform (https://wj.sjtu.edu.cn/) as a proxy for its SSO infrastructure, enabling secure, passwordless authentication. This flow is based on a One-Time Password (OTP) model, providing a robust and user-friendly experience for signup, login, and password recovery within a streamlined single-page application flow.
Authentication Flow
The process is designed as a linear, redirect-based flow that is compatible across all browsers and WebView environments.
-
Initiation and Bot Prevention
A user on a designated page (e.g.,
/signup) is verified by a Cloudflare Turnstile challenge. Upon success, the frontend sends aninitiaterequest to our backend (api.website.com), declaring the user's intent (action). -
OTP Generation and User Redirection
The backend validates the Turnstile response, generates a cryptographically secure 8-digit OTP, and associates it with a short-lived (10-minute)
temp_token. The backend sets thistemp_tokenin a secure,HttpOnlycookie and returns the OTP and the relevant pre-configured questionnaire URL. (Use different questionnaires for different actions.) The frontend prominently displays the OTP and a copy button. When the user clicks this button, the OTP is copied to their clipboard, frontend shows "Copied!", and they are automatically redirected to the questionnaire URL within the current tab after 1 second (no countdown or displaying remaining seconds). The OTP is also appended to the URL as aotp_hintparameter for user convenience. -
Identity Assertion via Questionnaire
The user is now on the university's questionnaire page. If required, they log in with their university credentials, paste or type the OTP into the form, and submit it.
-
Callback and State Hand-off
The questionnaire platform instantly redirects the user back to our unified callback URL (e.g.,
https://frontend.website.com/callback?action=signup&account={{.User}}&answer_id={{.AnswerID}}), which includes parameters specifying university account, a unique submission ID, and the original intent. The callback page sends this information to our backend for verification. The browser automatically includes thetemp_tokencookie in this request. -
Backend Validation
The backend performs a rigorous validation, confirming the submission details against the OTP and the
temp_token(read from the cookie) records stored in Redis. Upon success, the OTP is consumed, and thetemp_tokenis marked as verified, now containing the authenticated user's identity. -
Flow Completion
The backend returns a success response to the callback page. The callback page stores a non-sensitive state object (e.g.,
{ status: 'verified', action: 'signup', expires_at: ... }) inlocalStorageand redirects the user back to the original page they started from (e.g.,/signup). This page, now detecting the verified flow state inlocalStorage, intelligently renders the next step of the flow (e.g., the password creation form) instead of the initial OTP component.
Security Analysis
Overview
This authentication flow is hardened by its stateless, token-based design. It relies on short-lived, single-use credentials and verifiable, server-side intent, which mitigates common web application vulnerabilities.
Key Security Mechanisms
- Bot Prevention: Cloudflare Turnstile gates all authentication initiation points.
- Cryptographically Secure Tokens: The OTP and
temp_tokenare generated using a CSPRNG. HttpOnlyCookie Storage: Thetemp_tokenis stored in a secure,HttpOnlycookie, preventing access from client-side JavaScript and mitigating XSS-based token theft.- Time-Limited Validity: OTP is valid for 2 minutes;
temp_tokenfor 10 minutes. - One-Time-Use OTP: The OTP-to-token link is deleted from Redis immediately after use.
- Hashed Token Storage: The
temp_tokenis stored in Redis using its SHA256 hash as the key (temp_token_state:<sha256(temp_token)>). - Server-Side Intent Enforcement: The backend maps each
quest_idto a specificactionand enforces this link during verification.
Implementation Details
Components & Stack
- Frontend: Vue.js (
frontend.website.com) with Vue Router - Backend: Django / Django REST Framework (
api.website.com) - Cache/State Store: Redis
- Bot Prevention: Cloudflare Turnstile
- Infrastructure: Cloudflare in front of Nginx.
Frontend Architecture (Vue Components)
AuthInitiate.vue (Reusable Component)
- Props:
action(String:signup,login,reset_password). - Logic:
- Checks
localStoragefor a non-expired OTP record. If found, displays it to allow the user to re-copy. Expired OTP and auth flow state records inlocalStorageare cleared. - If no valid OTP exists, renders the Cloudflare Turnstile widget.
- On Turnstile success, calls
POST /api/auth/initiateendpoint with itsactionprop. - On receiving the
otpandredirect_url(thetemp_tokenis set as a cookie by the backend), stores{ otp, expires_at }and{ status: 'pending', expires_at }inlocalStorageto track the flow's state. - Displays the OTP and copy button. On click, it copies the OTP, provides visual feedback, and initiates the redirect to the URL received from backend.
- Checks
Page-Level Components
Signup.vue (/signup)
On mount, checks localStorage for an auth_flow state object with status: 'verified', action: 'signup', and a non-expired timestamp.
- If not found: Renders
<AuthInitiate action="signup" />. - If found: Renders
<SetPasswordForm action="signup" />.
Login.vue (/login)
Redirect to / if already logged in. Multiple login methods, including password login and questionnaire login, may be more in the future. Both requires passing Turnstile. Uncareful handling of Turnstile widget may cause conflicts. If questionnaire, render <AuthInitiate action="login" />. The flow completes at the callback, which redirects to the homepage directly.
ResetPassword.vue (/reset)
On mount, checks localStorage for an auth_flow state object with status: 'verified', action: 'reset_password', and a non-expired timestamp.
- If not found: Renders
<AuthInitiate action="reset_password" />. - If found: Renders
<SetPasswordForm action="reset_password" />.
AuthCallback.vue (/callback)
- Logic: A transient component.
- On mount, parses
account,answer_id, andactionfrom the URL query. - Calls
POST /api/auth/verifywith these parameters. The browser automatically sends thetemp_tokencookie. - On success, receives the flow's
actionandexpires_attimestamp from the backend and writes a state object{ status: 'verified', action, expires_at }tolocalStorage. - Redirects: if
action == 'login', redirect to/. Otherwise, redirect to/${action}(e.g.,/signup).
- On mount, parses
SetPasswordForm.vue (Reusable Component)
- Props:
action(String:signup,reset_password). - Logic: Renders password fields. (password and re-type-password only. Backend fetches username and other info directly from questionnaire platform.) On submit, it sends the new password to
POST /api/auth/signuporPOST /api/auth/password(for password reset) based on action. The browser automatically attaches theHttpOnlytemp_tokencookie to the request. The frontend does not handle the token. Delete OTP and the auth flow state fromlocalStorageon success and redirect to/.
Detailed Backend Process
POST /api/auth/initiate
- Input: Receives the user's intended
actionand theturnstile_token. - Validation: Verifies the
turnstile_tokenwith Cloudflare's API. - Generation: Generates a cryptographically secure
otpand atemp_token. - Redis Storage:
- Links the
otpto the rawtemp_tokenwith a 2-minute expiry. - Creates a state record for the
temp_token(keyed by its SHA256 hash). This record contains itsstatus(pending) and the user'saction, and has a 10-minute expiry.
- Links the
- Response:
200 OKwith theotpandredirect_url. The backend also sets a secure,HttpOnlycookie containing thetemp_tokenwith a 10-minute expiry.
POST /api/auth/verify
- Input: Receives the
account,answer_id, andactionfrom the callback. Thetemp_tokenis received via theHttpOnlycookie sent by the browser. - Token Pre-Validation: Extracts the
temp_tokenfrom the cookie. This is the fastest check to reject invalid, expired, or already-used tokens.- Looks up the state record in Redis using the SHA256 hash of the
temp_token. - Verifies the status is
pending. If it's alreadyverifiedor missing, return an error.
- Looks up the state record in Redis using the SHA256 hash of the
- Security: Applies strict rate limiting per valid
temp_tokento prevent brute-force attempts on a single verification flow. Check and set rate limit (or attempts count) in Redis. - API Query: Fetches recent submissions from the questionnaire platform for the given
account. - Find Submission: Locates the specific submission matching the
answer_id. If not found, returns an error. - Extract Data: Extracts the
submitted_otpand the questionnaire's unique ID (quest_id) from the submission. Intent Verification: Confirms that theNot needed, different quesitonnaires use different API, cross-flow attacks are not possible.quest_idfrom the submission correctly maps to theactionspecified in the request, preventing cross-flow attacks.- OTP & Token Link Validation:
- Atomically retrieves the expected
temp_tokenassociated with thesubmitted_otpand deletes the OTP record to prevent reuse. Use atomicGETDELto prevent race condition. - If no token is found for the OTP, or if the retrieved token does not exactly match the
temp_tokenfrom the cookie, the request is invalid. Return401 Unauthorized.
- Atomically retrieves the expected
- State Transition: Updates the state record's status to
verifiedand adds the authenticated user details (e.g.,jaccount,ip, time). The record's TTL is preserved. - Action-Specific Logic: If the
actionislogin, the user is now fully authenticated. The backend logs them in and immediately deletes thetemp_token_staterecord from Redis. - Response:
200 OKwith the confirmedaction, anexpires_attimestamp for the flow state, and a booleanis_logged_inflag. This allows the frontend to safely manage its UI state.
POST /api/auth/signup
- Input: Receives the new
password. Thetemp_tokenis received via theHttpOnlycookie. - Token Validation:
- Looks up the state record in Redis using the SHA256 hash of the
temp_tokenfrom the cookie. - Verifies the record exists and its status is
verified, and itsactionissignup.
- Looks up the state record in Redis using the SHA256 hash of the
- Password Complexity Check: Checks the password against complexity and length rules.
- Extract Identity: Retrieves the
jaccountandactionfrom the state record. - Logic: Creates a new user account, checking first that the
jaccountdoes not already exist. - Session Management: Logs in the user session.
- Cleanup: Deletes the
temp_token_staterecord from Redis and sends an instruction to the browser to clear thetemp_tokencookie (set cookie as already expired). - Response:
200 OKwith a success message confirming the action was completed.
POST /api/auth/password
- Input: Receives the new
password. Thetemp_tokenis received via theHttpOnlycookie. - Token Validation:
- Looks up the state record in Redis using the SHA256 hash of the
temp_tokenfrom the cookie. - Verifies the record exists and its status is
verified, and its action isreset_password. If not, return403 Forbidden.
- Looks up the state record in Redis using the SHA256 hash of the
- Password Complexity Check: Checks the password against complexity and length rules.
- Extract Identity: Retrieves the
jaccountandactionfrom the state record. Identity Verification: Verifies theForget password will fail this.jaccountis the same with the account of the current user (identified by session).- Password Update: updates the password of the authenticated user.
- Cleanup: Deletes the
temp_token_staterecord from Redis and sends an instruction to the browser to clear thetemp_tokencookie (set cookie as already expired). - Response:
200 OKwith a success message confirming the action was completed.