Configuration, scopes and best practices
Configuration
Environment Variables
The following environment variables control authentication behavior:
Token Configuration
| Variable | Description | Default | Required |
|---|---|---|---|
SECRET_KEY |
Secret key for JWT signing (min 32 characters recommended) | - | Yes |
ALGORITHM |
JWT signing algorithm | HS256 |
No |
ACCESS_TOKEN_EXPIRE_MINUTES |
Access token lifetime in minutes | 15 |
No |
REFRESH_TOKEN_EXPIRE_DAYS |
Refresh token lifetime in days | 7 |
No |
Session Configuration
| Variable | Description | Default | Required |
|---|---|---|---|
SESSION_IDLE_TIMEOUT_ENABLED |
Enable session idle timeout | false |
No |
SESSION_IDLE_TIMEOUT_HOURS |
Hours of inactivity before session expires | 1 |
No |
SESSION_ABSOLUTE_TIMEOUT_HOURS |
Maximum session lifetime in hours | 24 |
No |
Security Configuration
| Variable | Description | Default | Required |
|---|---|---|---|
ENVIRONMENT |
Controls refresh cookie security for login, refresh, and OAuth/SSO token exchange. production and demo set the Secure flag on the refresh cookie and the OAuth/SSO session cookie, which requires Endurain to be served over HTTPS in those environments for login, session refresh, and SSO to work. |
production |
No |
ALLOWED_REDIRECT_SCHEMES |
Comma-separated custom URI schemes allowed as SSO redirect targets (e.g., endurain,gadgetbridge). Defaults to endurain when unset. If set, the provided list is used as-is (it does not merge with the default). External http/https redirects are always rejected. |
endurain |
No |
SSRF_ALLOWED_HOSTS |
SSRF allowlist for admin-configured outbound calls (currently OIDC discovery and JWKS fetch only). Comma-separated list of exact hostnames (case-insensitive) and/or explicit IP CIDR ranges. Supports self-hosted identity providers on private networks. Examples: auth.internal.example.com or auth.internal.example.com,10.10.0.0/24,fd00::/64. Wildcards are rejected. IPv4 prefix must be ≥ /8, IPv6 ≥ /32. Every allowlisted outbound call is logged at INFO level for audit. |
`` | No |
Rate Limiting and Shared State
Rate-limit counters and all auth-security state (login/step-up lockout, pending
MFA login state, MFA setup secrets, Garmin MFA codes, websocket tickets) share
one backend selected by DEPLOYMENT_PROFILE.
| Variable | Description | Default | Required |
|---|---|---|---|
RATE_LIMIT_ENABLED |
Enable or disable API rate limiting. Set to false to disable for development or testing. |
true |
No |
DEPLOYMENT_PROFILE |
Deployment shape selecting the backend for all shared state. local (default) uses in-process memory; distributed requires Redis. A distributed or multi-worker deployment wired to memory is rejected at startup. |
local |
No |
WEB_WORKERS |
Number of web-server worker processes. When > 1, shared state (Redis) is required even under the local profile. |
1 |
No |
STATE_URI |
Explicit override for the shared ephemeral-state backend. Takes precedence over REDIS_URL. memory:// or redis://…. |
(profile default) | No |
REDIS_URL |
Shared Redis DSN backing every Redis capability with no explicit URI (including STATE_URI). Required for distributed or WEB_WORKERS > 1. |
(unset) | No |
ALLOW_API_KEY_QUERY_PARAM |
Allow API keys via the ?api_key= query parameter. Disabled by default because query-string credentials appear in server access logs, reverse-proxy logs, and browser history. Enable only for integrations that cannot set custom request headers. The X-API-Key header is always preferred. |
false |
No |
Cookie Configuration
For web clients, the refresh token cookie is configured with:
| Attribute | Value | Purpose |
|---|---|---|
| HttpOnly | true |
Prevents JavaScript access (XSS protection) |
| Secure | true when ENVIRONMENT=production or demo; applies consistently to login, refresh, and OAuth/SSO token exchange |
Only sent over HTTPS |
| SameSite | Strict |
Prevents CSRF attacks |
| Path | /api/v1/auth |
Sent only to auth endpoints that need the refresh token |
| Expires | 7 days (default) | Matches refresh token lifetime |
Security Scopes
Endurain uses OAuth-style scopes to control API access. Each scope controls access to specific resource groups:
Available Scopes
| Scope | Description |
|---|---|
profile |
Privileges over user's own profile |
users:read |
Read privileges over users |
users:write |
Write privileges over users |
sessions:read |
Read privileges over sessions |
sessions:write |
Create/edit/delete privileges over sessions |
gears:read |
Read privileges over gears |
gears:write |
Write privileges over gears |
activities:read |
Read privileges over activities |
activities:write |
Write privileges over activities |
activities:upload |
Upload privileges over activities |
followers:read |
Read privileges over the follower/following graph |
followers:write |
Follow, unfollow, accept and remove followers |
health:read |
Read privileges over health data |
health:write |
Write privileges over health data |
health_targets:read |
Read privileges over health targets data |
health_targets:write |
Write privileges over health targets data |
notifications:read |
Read privileges over notifications |
notifications:write |
Write privileges over notifications |
server_settings:read |
Read privileges over server settings |
server_settings:write |
Write privileges over server settings |
identity_providers:read |
Read privileges over identity providers |
identity_providers:write |
Write privileges over identity providers |
Scope Usage
Scopes are automatically assigned based on user permissions and are embedded in JWT tokens. API endpoints validate required scopes before processing requests.
Error responses
All errors, including authentication and authorization failures, use one problem-document shape. See Errors, concurrency and rate limits.
Best Practices
For Web Client Applications
- Store access and CSRF tokens in memory - Never persist in localStorage or sessionStorage
- Implement automatic token refresh - Refresh before access token expires (e.g., at 80% of lifetime)
- Handle concurrent refresh requests - Use a refresh lock pattern to prevent race conditions
- Always include required headers:
Authorization: Bearer {access_token}for all authenticated requestsX-Client-Type: webfor all requestsX-CSRF-Token: {csrf_token}for protected POST/PUT/DELETE/PATCH requests, except/auth/refreshbootstrap. Protected web writes require the header;/auth/refreshalso validates the token value when supplied and bound to the session
- Handle page reload gracefully - Call
/auth/refreshon app initialization to restore in-memory tokens - Clear tokens on logout - The httpOnly cookie is cleared by the backend
For Mobile Client Applications
- Store tokens securely:
- iOS: Keychain Services
- Android: EncryptedSharedPreferences or Android Keystore
- Use PKCE for OAuth/SSO - Required for OAuth/SSO flows
- Include required headers:
Authorization: Bearer {access_token}for all authenticated requestsX-Client-Type: mobilefor all requests
- Handle token refresh proactively - Refresh before expiration
- Implement secure token deletion on logout
For Security
- Never expose
SECRET_KEYin client code or version control - Use strong, randomly generated secrets (minimum 32 characters)
- Always use HTTPS in production environments
- Enable MFA for enhanced account security
- Monitor for token reuse - Indicates potential token theft
- Enable session idle timeout for sensitive applications
- Use appropriate scopes - Request only necessary permissions
For OAuth/SSO Integration
- Always use PKCE - Required for OAuth/SSO login
- Validate state parameter - Prevents CSRF attacks on OAuth flow
- Implement proper redirect URL validation - Prevents open redirects
- Handle provider errors gracefully with user-friendly messages
- Support account linking - Allow users to connect multiple providers
- Respect token expiry - OAuth state expires after 10 minutes
For API Key Integrations
- Store the key securely — use a secrets manager, environment variable, or encrypted config file. Never hardcode it in source code
- Use the
X-API-Keyheader rather than the?api_key=query parameter to avoid key exposure in logs. The query parameter is disabled by default and must be explicitly enabled withALLOW_API_KEY_QUERY_PARAM=true - Grant only supported scopes — API keys currently accept only
activities:upload - Set an expiry date when creating keys for temporary or one-off integrations
- Rotate keys periodically — delete the old key and create a new one; update any dependent services before deleting
- Revoke immediately if a key is suspected to be compromised — revocation takes effect instantly
- Monitor
last_used_atvia the list endpoint to detect unused keys that can be cleaned up