Authentication methods, API token types, JWT scopes, project roles, and permissions model
Authentication & Tokens
Flaggr supports multiple authentication methods for different use cases -- Firebase session auth for the dashboard, and API tokens (opaque or JWT) for programmatic access.
Authentication Methods
Dashboard (Firebase Auth)
The Flaggr dashboard uses Firebase Authentication. Users sign in via Google, GitHub, or email/password credentials. Sessions are managed via secure HTTP-only cookies.
API Tokens (Bearer)
For programmatic access from SDKs, CI/CD pipelines, and service-to-service communication:
Authorization: Bearer flg_your_token
Public Flags
Flags marked as isPublic: true can be evaluated without any authentication. Useful for client-side feature flags where embedding a token isn't appropriate.
Token Types
Opaque Tokens
Simple bearer tokens with hash-based permission storage. Best for straightforward SDK usage.
POST /api/projects/{id}/tokens
{
"name": "CI Read Token",
"permissions": {
"read": true,
"write": false,
"delete": false
},
"expiresAt": "2026-12-31T00:00:00Z"
}Response (token value shown only on creation):
{
"token": {
"id": "tok-abc123",
"name": "CI Read Token",
"tokenType": "opaque",
"permissions": { "read": true, "write": false, "delete": false },
"expiresAt": "2026-12-31T00:00:00Z",
"createdAt": "2025-07-20T10:00:00Z"
},
"value": "flg_abc123def456..."
}Store the value securely -- it cannot be retrieved again.
JWT Tokens
Modern tokens with OAuth2-style scopes. Provide access/refresh token pairs with automatic expiration.
POST /api/projects/{id}/tokens
{
"name": "Service JWT",
"scopes": ["read", "write"]
}Response:
{
"token": {
"id": "tok-xyz789",
"name": "Service JWT",
"tokenType": "jwt",
"scopes": ["read", "write"],
"createdAt": "2025-07-20T10:00:00Z"
},
"accessToken": "eyJhbGciOiJ...",
"refreshToken": "eyJhbGciOiJ...",
"accessTokenExpiresAt": "2025-07-21T10:00:00Z",
"refreshTokenExpiresAt": "2025-08-20T10:00:00Z"
}JWT Claims Structure
When decoded, a Flaggr JWT access token contains the following claims:
{
"sub": "tok-xyz789",
"iss": "flaggr",
"aud": "flaggr-api",
"iat": 1721473200,
"exp": 1721559600,
"jti": "unique-token-id-abc",
"projectId": "proj_a1b2c3d4",
"tokenId": "tok-xyz789",
"scopes": ["read", "write"],
"type": "access"
}| Claim | Description |
|---|---|
sub | Token ID (subject) |
iss | Always "flaggr" |
aud | Always "flaggr-api" |
iat | Issued-at timestamp (Unix seconds) |
exp | Expiration timestamp (Unix seconds) |
jti | Unique token identifier for revocation tracking |
projectId | The project this token belongs to |
scopes | Array of granted scopes |
type | "access" or "refresh" |
Token Lifecycle
Create token → Receive access + refresh tokens
│
├─ Access token (24h TTL)
│ ├─ Use for API requests
│ ├─ Include in Authorization header
│ └─ On 401 → use refresh token
│
└─ Refresh token (30d TTL)
├─ Exchange for new access + refresh pair
├─ Old refresh token is invalidated (rotation)
└─ If expired → create new token from dashboard
Access tokens expire after 24 hours. Refresh tokens expire after 30 days. When you refresh, both the access and refresh tokens are rotated -- the old refresh token is invalidated immediately to prevent replay attacks.
Refreshing JWT Tokens
POST /api/projects/{id}/tokens/refresh
Authorization: Bearer {refreshToken}Returns new access and refresh tokens:
{
"accessToken": "eyJhbGciOiJ...(new)",
"refreshToken": "eyJhbGciOiJ...(new)",
"accessTokenExpiresAt": "2025-07-22T10:00:00Z",
"refreshTokenExpiresAt": "2025-08-21T10:00:00Z"
}Each refresh token is single-use. After a successful refresh, the previous refresh token is invalidated. If you detect a reused refresh token, Flaggr revokes the entire token family as a security precaution.
Recommended Refresh Pattern
SDKs should implement proactive refresh to avoid interruptions:
// Refresh when the access token has < 5 minutes remaining
const REFRESH_BUFFER_MS = 5 * 60 * 1000
async function ensureValidToken(tokenState: TokenState): Promise<string> {
const now = Date.now()
const expiresAt = tokenState.accessTokenExpiresAt.getTime()
if (expiresAt - now > REFRESH_BUFFER_MS) {
return tokenState.accessToken // Still valid
}
// Proactively refresh
const response = await fetch(`/api/projects/${projectId}/tokens/refresh`, {
method: 'POST',
headers: { Authorization: `Bearer ${tokenState.refreshToken}` },
})
const { accessToken, refreshToken, accessTokenExpiresAt } = await response.json()
tokenState.accessToken = accessToken
tokenState.refreshToken = refreshToken
tokenState.accessTokenExpiresAt = new Date(accessTokenExpiresAt)
return accessToken
}Permissions Model
Project Roles
| Role | Flags | Services | Members | Settings | Delete Project |
|---|---|---|---|---|---|
| Owner | Read/Write/Delete | Read/Write/Delete | Manage | Manage | Yes |
| Admin | Read/Write/Delete | Read/Write/Delete | Manage | Manage | No |
| Member | Read/Write | Read/Write | View | No | No |
| Viewer | Read | Read | View | No | No |
Token Scopes
| Scope | Opaque Permission | JWT Scope | Grants |
|---|---|---|---|
| Read flags | permissions.read | read | Evaluate flags, list flags, view configs |
| Write flags | permissions.write | write | Create/update flags, toggle, manage targeting |
| Delete flags | permissions.delete | delete | Delete flags |
| Manage settings | -- | manage_settings | Project settings, tokens |
| Manage members | -- | manage_members | Add/remove members, change roles |
CSRF Protection
All state-changing requests from the dashboard require a CSRF token:
# 1. Get CSRF token
GET /api/csrf-token
# → { "token": "abc123", "headerName": "x-csrf-token" }
# 2. Include in mutation requests
POST /api/flags
X-CSRF-Token: abc123
Content-Type: application/json
{ ... }API token authentication does not require CSRF tokens -- CSRF protection only applies to cookie-based sessions.
Token Management
List All Tokens for a Project
GET /api/projects/{id}/tokensList Your Tokens Across All Projects
GET /api/users/me/tokensRevoke a Token
DELETE /api/projects/{id}/tokens/{tokenId}Revoked tokens are immediately invalidated. For JWT tokens, the jti claim is added to a revocation list that is checked on every request. Revocation list entries are automatically cleaned up after the token's original expiration time passes.
Authentication Flow Diagrams
SDK Authentication (Opaque Token)
SDK Flaggr API
│ │
├─ POST /api/flags/evaluate ──────────►│
│ Authorization: Bearer flg_xxx │
│ ├─ Hash token
│ ├─ Lookup in token store
│ ├─ Check permissions
│ ├─ Check expiration
│ ├─ Evaluate flag
│◄──────────── 200 OK ────────────────┤
│ { value: true, reason: "..." } │
SDK Authentication (JWT Token)
SDK Flaggr API
│ │
├─ POST /api/flags/evaluate ──────────►│
│ Authorization: Bearer eyJhbG... │
│ ├─ Verify JWT signature
│ ├─ Check exp claim
│ ├─ Check jti not revoked
│ ├─ Extract scopes
│ ├─ Evaluate flag
│◄──────────── 200 OK ────────────────┤
│ │
│ ... 24h later, access token expired │
│ │
├─ POST /tokens/refresh ──────────────►│
│ Authorization: Bearer refresh_tok │
│ ├─ Verify refresh JWT
│ ├─ Rotate tokens
│◄──────── New access + refresh ──────┤
Best Practices
- Use read-only tokens for SDKs -- evaluation only needs
readpermission - Set expiration dates on tokens, especially for CI/CD
- Use JWT tokens for service-to-service communication (automatic expiration + refresh)
- Use opaque tokens for simple SDK usage (no refresh management needed)
- Rotate tokens regularly -- create a new token before revoking the old one
- Use public flags for client-side feature flags to avoid embedding tokens in frontend code
- Never commit tokens to version control -- use environment variables
- Implement proactive refresh for JWT tokens to avoid request failures at expiration boundaries
Related
- Quick Start -- Generate your first API token
- REST API Reference -- Token management endpoints
- Environments -- Per-environment access control