This template implements a stateless JWT(JWS) authentication system instead of stateful session-based authentication. Authentication processing is performed entirely on the backend, and the mobile client is responsible only for storing and transmitting tokens.
sequenceDiagram
participant User
participant Mobile
participant OAuthProvider
participant Backend
participant API
User->>OAuthProvider: Social Login (OAuth)
OAuthProvider->>Mobile: OAuth Access Token
Mobile->>Backend: POST /api/v1/auth/login (OAuth Token)
Backend->>Backend: OAuth Token Re-verification (Provider API)
Backend->>Backend: User Creation/Update (DB)
Backend->>Backend: JWT Token Issuance (access + refresh)
Backend->>Mobile: Return JWT Token
Mobile->>API: Authorization Header (access_token)
API->>API: JWT Token Verification
API->>User: Return Protected Resource
Essential Files:
src/lib/auth.py- JWT token generation/validation, OAuth validationsrc/auth/router.py- Authentication endpointssrc/users/model.py- User DB modelsrc/lib/dependencies.py- Dependency injection for auth
Key Functions:
create_access_token(user_id)- Create JWT access token (1 hour expiry)create_refresh_token(user_id)- Create JWT refresh token (7 days expiry)decode_token(token)- Verify JWT token and extract payloadverify_oauth_token(provider, token)- Re-verify OAuth token (Google/GitHub/Facebook)get_current_user(request)- Extract user from Authorization header
Endpoints:
POST /api/v1/auth/login- OAuth loginPOST /api/v1/auth/refresh- Refresh tokenPOST /api/v1/auth/logout- Logout
Security:
- JWT Signing (HS256 / HMAC-SHA256)
- Access Token: 1 hour expiry
- Refresh Token: 7 days expiry
- Authorization header based transmission
- Format: JWT (JSON Web Token, JWS)
- Algorithm: HS256 (HMAC-SHA256)
- Expiry: 1 hour
- Storage: Secure storage on mobile device
- Usage:
Authorization: Bearer {token}header in API requests
- Format: JWT (JWS)
- Algorithm: HS256
- Expiry: 7 days
- Storage: Secure storage on mobile device
- Usage: Used to renew access token when expired
User: Tap "Google Login"
↓
Mobile: OAuth provider redirect
↓
OAuthProvider: OAuth access token issued
↓
Mobile: OAuth access token received
↓
Mobile: POST /api/v1/auth/login { provider, access_token }
↓
Backend: OAuth token re-verification (Google API)
↓
Backend: User DB lookup/creation
↓
Backend: JWT token issuance (access: 1h, refresh: 7d)
↓
Mobile: JWT token secure storage
Mobile: API Request
↓
HTTP Client: access_token auto add to Authorization header
↓
Backend: Authorization header verification
↓
Backend: JWT token decoding
↓
Backend: user_id extraction
↓
Backend: User lookup in DB
↓
API: Protected resource return
Access Token Expired (1 hour)
↓
401 error on API request
↓
HTTP Client: auto use refresh_token
↓
Backend: POST /api/v1/auth/refresh
↓
Backend: New access_token issuance
↓
Mobile: Secure storage update
↓
Request auto retry
User: Tap "Logout"
↓
Mobile: Clear stored tokens
↓
Mobile: POST /api/v1/auth/logout
↓
Backend: Logout processing (client token invalidation if needed)
- HMAC-SHA256: Signs the payload with a shared secret key
- Algorithm: HS256
- Integrity: Signature ensures the token has not been tampered with
- HTTPS: Transport-layer encryption protects the token payload in transit
- No Server Session: No need to store session state on the server
- Easy Scaling: Easy load balancing
- Scale Out: Easy to add more servers
- Access Token: Short expiry (1 hour) - Security optimization
- Refresh Token: Long expiry (7 days) - User convenience
- Auto Refresh: Automatic renewal upon expiry
class User(Base):
id: UUID (PK)
email: String (unique, indexed)
name: String (nullable)
image: String (nullable)
email_verified: Boolean (default: False)
provider: String (nullable) # OAuth provider: google | github | facebook
provider_id: String (nullable) # Provider-specific user ID
role: String (default: "host") # host | concierge | care_worker | organization
created_at: DateTime
updated_at: DateTime# JWT (stateless authentication)
JWT_SECRET=strong-secret-key-32-chars-or-more
# Database
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5433/juny
# CORS
CORS_ORIGINS=http://localhost:3200,http://localhost:8080Purpose: Exchange OAuth token for Backend JWT
Request Body:
{
"provider": "google" | "github" | "facebook",
"access_token": "<OAuth provider token>"
}Email and name are obtained by re-verifying the OAuth token with the provider API.
Response:
{
"access_token": "<JWT signed access token>",
"refresh_token": "<JWT signed refresh token>",
"token_type": "bearer"
}Purpose: Issue new access token using refresh token
Request Body:
{
"refresh_token": "<JWT signed refresh token>"
}Response:
{
"access_token": "<JWT signed new access token>",
"refresh_token": "<JWT signed refresh token>",
"token_type": "bearer"
}Purpose: Client-side token cleanup
Response: 204 No Content
| Provider | Client ID Environment Variable | Client Secret Environment Variable | API Endpoint |
|---|---|---|---|
GOOGLE_CLIENT_ID |
GOOGLE_CLIENT_SECRET |
https://www.googleapis.com/oauth2/v3/userinfo |
|
| GitHub | GITHUB_CLIENT_ID |
GITHUB_CLIENT_SECRET |
https://api.github.com/user |
FACEBOOK_CLIENT_ID |
FACEBOOK_CLIENT_SECRET |
https://graph.facebook.com/v19.0/me?fields=id,name,email,picture |
Note: The current mobile app UI uses Google OAuth only.
- Minimal overhead with stateless JWT verification
- Reduced backend load
- Easy scaling due to stateless server
- Easy load balancing
- Authorization header method is optimal for mobile
- Simpler than cookie-based authentication
- JWT tokens can be inspected at jwt.io for debugging
- Standard format compatible with all languages and frameworks
Q: Why use JWT(JWS)?
A: The token payload contains only user_id and role — no sensitive data that requires encryption. JWT(JWS) with HS256 signing provides integrity verification while enabling easier debugging (jwt.io), better library support across platforms, and simpler Flutter/mobile integration. HTTPS already protects the token in transit.
Q: Why re-verify the OAuth token? A: To reinforce security by re-confirming user information through the OAuth provider API. It helps mitigate attacks in case of token theft.
Q: Why is the access token expiry time 1 hour? A: A short expiry time is important for security. It minimizes the damage scope if a token is compromised. It can be renewed using the refresh token (7 days).
Last Updated: 2026-03-03