Authentication
TrailBase provides a complete authentication system with JWT tokens, OAuth integration, email verification, and flexible access control.
Overview
The authentication system supports multiple flows:
Email/Password Authentication : Traditional username and password login
OAuth Providers : Google, GitHub, Discord, Microsoft, and more
JWT Tokens : Stateless authentication with EdDSA signatures
Refresh Tokens : Long-lived tokens stored in secure cookies
Email Verification : Account verification workflow
Password Reset : Secure password recovery
Auth Module All authentication logic is implemented in crates/core/src/auth/.
User Model
Database Schema
Users are stored in the _user table:
// From auth/user.rs:14-42
pub struct DbUser {
pub id : [ u8 ; 16 ], // UUIDv7 as bytes
pub email : String ,
pub password_hash : String , // Argon2 hash
pub verified : bool ,
pub admin : bool ,
pub created : i64 ,
pub updated : i64 ,
// Email verification
pub email_verification_code : Option < String >,
pub email_verification_code_sent_at : Option < i64 >,
pub pending_email : Option < String >,
// Password reset
pub password_reset_code : Option < String >,
pub password_reset_code_sent_at : Option < i64 >,
// OAuth authorization
pub authorization_code : Option < String >,
pub authorization_code_sent_at : Option < i64 >,
pub pkce_code_challenge : Option < String >,
// OAuth provider data
pub provider_id : i64 ,
pub provider_user_id : Option < String >,
pub provider_avatar_url : Option < String >,
}
Authenticated User
Once authenticated, users are represented by the User struct:
// From auth/user.rs:86-96
pub struct User {
pub id : String , // Base64-encoded UUID
pub email : String ,
pub uuid : Uuid , // Convenience UUID representation
pub csrf_token : String , // CSRF protection token
}
The User type can be extracted from Axum request handlers using FromRequestParts.
JWT Tokens
JWT Helper JWT token generation and validation using EdDSA (Ed25519) signatures.
Token Structure
// From auth/jwt.rs:29-49
pub struct TokenClaims {
pub sub : String , // Base64 encoded user ID
pub iat : i64 , // Issued at (Unix timestamp)
pub exp : i64 , // Expiration (Unix timestamp)
pub email : String , // User's email address
pub csrf_token : String , // Random CSRF token
pub admin : bool , // Is admin user
}
Key Management
JWT keys are automatically generated on first startup:
Private Key : traildepot/secrets/key/private_key.pem (EdDSA)
Public Key : traildepot/secrets/key/public_key.pem
// From auth/jwt.rs:89-98
pub struct JwtHelper {
header : Header ,
validation : Validation ,
encoding_key : EncodingKey , // For creating tokens
decoding_key : DecodingKey , // For validating tokens
public_key : String ,
}
Never commit your private key to version control. It’s automatically excluded via .gitignore.
Token Lifetimes
From constants.rs:
pub const DEFAULT_AUTH_TOKEN_TTL : Duration = Duration :: minutes ( 60 );
pub const DEFAULT_REFRESH_TOKEN_TTL : Duration = Duration :: days ( 30 );
Auth Token : Short-lived (60 minutes), sent in headers
Refresh Token : Long-lived (30 days), stored in secure HTTP-only cookie
Authentication Flows
Email/Password Registration
1. POST /api/auth/v1/register
Body: { "email": "[email protected] ", "password": "secure123" }
2. Server creates unverified user
3. Email verification code sent
4. Response: { auth_token, refresh_token, user_id }
Users can log in even without email verification, but some features may require verified=true.
Email/Password Login
1. POST /api/auth/v1/login
Body: { "email": "[email protected] ", "password": "secure123" }
2. Server validates credentials with Argon2
3. Lookup user sessions
4. Generate JWT tokens
5. Response: { auth_token, refresh_token, user_id }
Implemented in auth/api/login.rs using login_with_password() from auth/util.rs.
OAuth Flow
OAuth Integration Supports multiple OAuth providers with PKCE for security.
Supported providers:
Google
GitHub
GitLab
Discord
Microsoft
Facebook
Twitch
Yandex
Generic OIDC (OpenID Connect)
OAuth Flow :
1. GET /api/auth/v1/oauth/login/{provider}
Query: ?redirect_uri=<app_callback>&code_challenge=<pkce>
2. Server redirects to provider's authorization page
3. User authorizes on provider site
4. Provider redirects to: /api/auth/v1/oauth/callback/{provider}
Query: ?code=<auth_code>&state=<state>
5. Server exchanges code for tokens
6. Fetch user profile from provider
7. Create or update user in _user table
8. Redirect to app with authorization code
9. Client: POST /api/auth/v1/token
Body: { "code": "<auth_code>", "code_verifier": "<pkce_verifier>" }
10. Response: { auth_token, refresh_token, user_id }
Token Refresh
1. POST /api/auth/v1/refresh
Cookie: refresh_token=<token>
2. Server validates refresh token
3. Check session validity
4. Generate new auth token
5. Response: { auth_token, refresh_token }
Implemented in auth/api/refresh.rs.
Password Security
Password Hashing Passwords are hashed using Argon2id with secure parameters.
// Password hashing uses Argon2id
pub fn hash_password ( password : & str ) -> Result < String , AuthError > {
let salt = SaltString :: generate ( & mut OsRng );
let argon2 = Argon2 :: default ();
let hash = argon2
. hash_password ( password . as_bytes (), & salt )
. map_err ( | _ | AuthError :: Internal ( "password hashing" )) ? ;
Ok ( hash . to_string ())
}
Password Requirements (configurable):
Minimum length
Complexity rules
Common password prevention
Email Verification
Verification Flow
1. User registers account
2. Server generates random verification code
3. Email sent with verification link
4. GET /api/auth/v1/verify_email/confirm/{code}
5. Server marks user as verified
6. Redirect to application
Re-send Verification
GET /api/auth/v1/verify_email/trigger
Authorization: Bearer <auth_token>
Password Reset
Reset Flow
1. POST /api/auth/v1/reset_password/request
Body: { "email": "[email protected] " }
2. Server generates reset code
3. Email sent with reset link
4. POST /api/auth/v1/reset_password/update
Body: {
"email": "[email protected] ",
"code": "<reset_code>",
"new_password": "new_secure123"
}
5. Password updated, user can log in
Reset codes expire after a configured time period (default: 1 hour).
Sessions
Sessions are stored in the _session table:
CREATE TABLE _session (
id BLOB PRIMARY KEY NOT NULL ,
user_id BLOB NOT NULL REFERENCES _user(id),
refresh_token TEXT NOT NULL ,
csrf_token TEXT NOT NULL ,
created INTEGER NOT NULL ,
expires INTEGER NOT NULL
) STRICT;
Session Management
Creation : New session created on login
Validation : Checked on token refresh
Logout : Deletes session and invalidates refresh token
Expiration : Sessions auto-expire based on expires timestamp
Access Control
Table-Level ACLs
Access Control Lists define base permissions for unauthenticated and authenticated users.
record_apis {
name: "posts"
table_name: "post"
# Unauthenticated users can READ
acl_world: [READ]
# Authenticated users can do everything
acl_authenticated: [CREATE, READ, UPDATE, DELETE, SCHEMA]
}
Permissions from records/mod.rs:103-110:
pub enum Permission {
Create = 1 , // Create new records
Read = 2 , // Read records
Update = 4 , // Update records
Delete = 8 , // Delete records
Schema = 16 , // View JSON schema
}
Row-Level Access Rules
Access Rules SQL expressions that control access to individual records.
Access rules are SQL expressions with special variables:
_USER_: Current authenticated user (or NULL)
_ROW_: The record being accessed
_REQ_: The request payload (for CREATE/UPDATE)
Example Rules :
# Anyone can read all posts
read_access_rule: "TRUE"
# Only authenticated users can create
create_access_rule: "_USER_.id IS NOT NULL"
# Only post owner can update
update_access_rule: "_ROW_.user_id = _USER_.id"
# Only owner or admin can delete
delete_access_rule: "_ROW_.user_id = _USER_.id OR _USER_.admin = 1"
Complex Example :
# Can only publish own posts, admins can publish any
update_access_rule: |
_ROW_.user_id = _USER_.id AND (
_REQ_.published IS NULL OR
_REQ_.published = _ROW_.published OR
_USER_.admin = 1
)
CSRF Protection
Admin API endpoints require CSRF token validation to prevent cross-site request forgery.
CSRF protection flow:
Auth token includes csrf_token in claims
Client extracts token from auth token
Client sends token in x-csrf-token header
Server validates token matches user’s token
// From server/mod.rs:572-597
async fn assert_admin_api_access (
State ( state ) : State < AppState >,
mut req : Request ,
next : Next ,
) -> Result < Response , AuthError > {
let user = req . extract_parts_with_state :: < User , _ >( & state ) . await ? ;
if ! is_admin ( & state , & user . uuid) . await {
return Err ( AuthError :: Forbidden );
}
// Validate CSRF token
let Some ( received_csrf_token ) = req
. headers ()
. get ( HEADER_CSRF_TOKEN )
. and_then ( | header | header . to_str () . ok ())
else {
return Err ( AuthError :: BadRequest ( "admin APIs require csrf header" ));
};
if user . csrf_token != received_csrf_token {
return Err ( AuthError :: BadRequest ( "invalid CSRF token" ));
}
Ok ( next . run ( req ) . await )
}
Rate Limiting
Rate Limiter IP-based rate limiting prevents authentication abuse.
Auth endpoints have rate limiting:
Burst : 5 requests (50 in debug mode)
Replenish : 1 request every 2 seconds
Scope : POST methods only (login, register, etc.)
Key : Client IP address from headers or connection
// Rate limiter configuration
let governor_conf = GovernorConfigBuilder :: default ()
. burst_size ( 5 )
. per_second ( 2 )
. key_extractor ( RealIpKeyExtractor )
. use_headers ()
. methods ( vec! [ Method :: POST ])
. finish () ? ;
Configuration
Auth Options
auth {
# Disable password authentication (OAuth only)
disable_password_auth: false
# Custom URI schemes for mobile deep links
custom_uri_schemes: ["myapp"]
# OAuth providers
oauth_providers {
key: "google"
value {
provider_id: GOOGLE
client_id: "<google_client_id>"
client_secret: "<google_client_secret>"
}
}
}
OAuth Provider Setup
Google OAuth
Create project in Google Cloud Console
Enable Google+ API
Create OAuth 2.0 credentials
Set redirect URI: <site_url>/api/auth/v1/oauth/callback/google
GitHub OAuth
Go to Settings > Developer settings > OAuth Apps
Register new application
Set callback URL: <site_url>/api/auth/v1/oauth/callback/github
Copy client ID and secret
Best Practices
Use HTTPS Always use TLS in production to protect tokens in transit
Short Auth Tokens Keep auth token TTL short (minutes), rely on refresh tokens
Validate Email Require email verification for sensitive operations
Implement Logout Always provide logout to invalidate sessions
Security Considerations
Token Storage : Store refresh tokens in HTTP-only, secure cookies
Token Rotation : Refresh tokens are rotated on each use
Session Tracking : All sessions are tracked in database
IP Validation : Consider validating IP for sensitive operations
Admin Separation : Admin endpoints require both auth + CSRF
Next Steps
APIs Learn how to make authenticated API requests
Data Model Understand how access control integrates with data