Skip to main content

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"
}
ClaimDescription
subToken ID (subject)
issAlways "flaggr"
audAlways "flaggr-api"
iatIssued-at timestamp (Unix seconds)
expExpiration timestamp (Unix seconds)
jtiUnique token identifier for revocation tracking
projectIdThe project this token belongs to
scopesArray 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"
}
Refresh token rotation

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.

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

RoleFlagsServicesMembersSettingsDelete Project
OwnerRead/Write/DeleteRead/Write/DeleteManageManageYes
AdminRead/Write/DeleteRead/Write/DeleteManageManageNo
MemberRead/WriteRead/WriteViewNoNo
ViewerReadReadViewNoNo

Token Scopes

ScopeOpaque PermissionJWT ScopeGrants
Read flagspermissions.readreadEvaluate flags, list flags, view configs
Write flagspermissions.writewriteCreate/update flags, toggle, manage targeting
Delete flagspermissions.deletedeleteDelete flags
Manage settings--manage_settingsProject settings, tokens
Manage members--manage_membersAdd/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}/tokens

List Your Tokens Across All Projects

GET /api/users/me/tokens

Revoke 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 read permission
  • 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