Skip to content

API Authentication

Salient's REST API uses OAuth 2.0 for authentication and JWT tokens for session management, with role-based access control (RBAC) on all mutation endpoints.

OAuth Providers

Two OAuth providers are supported:

Provider Callback URL Setup
GitHub /auth/github/callback Create OAuth App in GitHub Developer Settings
Google /auth/google/callback Create OAuth Client in Google Cloud Console

Users authenticate via the login page, which redirects to the chosen provider. After authorization, the backend issues a JWT token.

JWT Tokens

Property Value
Expiry 7 days
Storage httpOnly cookie (primary) + localStorage (fallback)
Algorithm HS256
Claims user_id, email, role, org_id, exp

SECRET_KEY

Set SECRET_KEY in .env for production. Without it, the key is randomly generated on each restart, invalidating all existing sessions.

Using the API

The frontend uses httpOnly cookies automatically. No manual token handling required.

Bearer Token (API/MCP)

For programmatic access and MCP tools, include the JWT as a Bearer token:

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  https://your-domain.com/api/dashboard/

The MCP server reads the token from the SALIENT_TOKEN environment variable.

Role-Based Access Control

All mutation routes enforce RBAC via the require_role() dependency:

Role Permissions
Owner Full access — all CRUD operations, settings, member management, data deletion
Admin Exercise management, connector configuration, scoring, playbook generation
Member Read access, participate in exercises, view reports
# Backend enforcement example
@router.post("/api/connectors/okta/sync/")
async def okta_sync(user=Depends(require_role("admin"))):
    ...

Email Allowlist

Access can be restricted to specific email addresses via the ALLOWED_EMAILS environment variable:

ALLOWED_EMAILS=alice@company.com,bob@company.com

Users not on the allowlist cannot create accounts, even with valid OAuth credentials.

IP Restriction

The Caddy reverse proxy enforces IP-based access control via ALLOWED_IPS in .env. Requests from non-allowed IPs are rejected before reaching the backend.

For dynamic IPs, use the self-service update endpoint:

curl -X POST https://your-domain.com/auth/update-ip \
  -H "Content-Type: application/json" \
  -d '{"token": "YOUR_UPDATE_IP_TOKEN"}'

API Endpoints · Self-Hosting