Skip to main content

Overview

Risk Legion uses JWT (JSON Web Tokens) for API authentication. Tokens are obtained from Supabase Auth and must be included in all API requests.

Obtaining Tokens

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);

// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'password123'
});

if (error) throw error;

// Get access token
const token = data.session.access_token;

Via Script (Testing)

For testing purposes, use the provided script:
cd backend

python scripts/get_token.py \
  --email user@example.com \
  --password YourPassword123!
Output:
Access Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Expires: 2026-01-16T11:30:00Z

Using Tokens

HTTP Header

Include the token in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

cURL Example

curl -X GET "https://api.risklegion.com/api/v1/bras" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json"

JavaScript/TypeScript Example

const response = await fetch('https://api.risklegion.com/api/v1/bras', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
});

Token Lifecycle

Token Expiration

Token TypeLifetimeRefresh
Access Token1 hourUse refresh token
Refresh Token7 daysRe-authenticate

Automatic Refresh

The Supabase client automatically refreshes tokens:
// Tokens are automatically refreshed
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'TOKEN_REFRESHED') {
    // Update your token reference
    currentToken = session.access_token;
  }
});

Manual Refresh

const { data, error } = await supabase.auth.refreshSession();
if (data.session) {
  const newToken = data.session.access_token;
}

Authentication Errors

401 Unauthorized

Token is missing, expired, or invalid:
{
  "error": "Invalid or expired token",
  "code": "UNAUTHORIZED"
}
Solutions:
  • Check if token is included in request
  • Refresh the token if expired
  • Re-authenticate if refresh fails

403 Forbidden

Token is valid but user lacks permission:
{
  "error": "Insufficient permissions for this action",
  "code": "FORBIDDEN"
}
Solutions:
  • Verify user has correct role
  • Check entity assignments for the user
  • Contact admin for access

Security Best Practices

  • Store tokens securely (memory or secure storage)
  • Never store tokens in localStorage for sensitive apps
  • Clear tokens on sign out
  • Don’t expose tokens in URLs or logs
  • Always use HTTPS
  • Include token only in Authorization header
  • Don’t pass tokens as query parameters
  • Validate HTTPS certificates
  • Validate token expiration before requests
  • Implement automatic refresh logic
  • Handle authentication errors gracefully
  • Re-authenticate when refresh fails
  • Verify JWT signature on every request
  • Check token audience and issuer
  • Validate token expiration
  • Log authentication failures

Test User Creation

Create Test Users

For development and testing:
cd backend

# Create Super Admin
python scripts/create_test_user.py \
  --email superadmin@test.com \
  --password SuperAdmin123! \
  --role super_admin

# Create Client Admin
python scripts/create_test_user.py \
  --email admin@acme.com \
  --password Admin123! \
  --role admin \
  --enterprise "ACME Corporation"

# Create Assessor
python scripts/create_test_user.py \
  --email assessor@acme.com \
  --password Assessor123! \
  --role assessor \
  --enterprise "ACME Corporation"

# Create Reviewer
python scripts/create_test_user.py \
  --email reviewer@acme.com \
  --password Reviewer123! \
  --role reviewer \
  --enterprise "ACME Corporation"

Role-Based Access

Different roles have different API access:
RoleAPI Access
Super Admin/api/v1/admin/* endpoints
Client AdminAll enterprise endpoints
AssessorBRA, Controls, Actions (assigned entities)
ReviewerRead-only access (assigned entities)
See RBAC for detailed permissions.

Postman Setup

Environment Variables

{
  "variables": [
    {
      "key": "base_url",
      "value": "https://api.risklegion.com"
    },
    {
      "key": "access_token",
      "value": ""
    }
  ]
}

Pre-request Script

// Auto-refresh token before requests
const tokenExpiry = pm.environment.get('token_expiry');
const now = Date.now();

if (tokenExpiry && now > tokenExpiry - 60000) {
  // Token expiring soon, refresh it
  pm.sendRequest({
    url: pm.environment.get('supabase_url') + '/auth/v1/token?grant_type=refresh_token',
    method: 'POST',
    header: {
      'Content-Type': 'application/json',
      'apikey': pm.environment.get('supabase_anon_key')
    },
    body: {
      mode: 'raw',
      raw: JSON.stringify({
        refresh_token: pm.environment.get('refresh_token')
      })
    }
  }, function (err, res) {
    if (!err) {
      const data = res.json();
      pm.environment.set('access_token', data.access_token);
      pm.environment.set('token_expiry', Date.now() + (data.expires_in * 1000));
    }
  });
}

Troubleshooting

Common Issues

  • Verify token format: Bearer <token>
  • Check for whitespace in token
  • Ensure token is for correct environment
  • Verify token hasn’t expired
  • User may not be assigned to an enterprise
  • Check user role in enterprise_users table
  • Verify entity assignments for Assessors/Reviewers
  • Refresh token may be expired (7 days)
  • User may have been deactivated
  • Session may have been revoked
  • Re-authenticate from scratch