Skip to main content

Overview

OpsHub NAV uses Supabase Auth with JWT tokens for authentication. All API requests must include a valid authentication token.

Authentication Flow

1

Sign In

Authenticate with email/password or OAuth provider
2

Receive JWT

Get access token and refresh token
3

Include in Requests

Add token to Authorization header
4

Refresh Token

Use refresh token to get new access token before expiry

API Key Authentication

For server-to-server communication, use API keys:

Generate API Key

curl -X POST https://api.opshub.nomark.ai/v1/auth/api-keys \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production API Key",
    "scopes": ["portfolios:read", "portfolios:write"]
  }'

Use API Key

curl https://api.opshub.nomark.ai/v1/portfolios \
  -H "Authorization: Bearer YOUR_API_KEY"
Store API keys securely. Never commit them to version control or expose them in client-side code.

Email/Password Authentication

Sign Up

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

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: {
      full_name: 'John Doe',
      organization: 'ACME Investments'
    }
  }
});

Sign In

const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password'
});

// Access token available at
const accessToken = data.session?.access_token;
const refreshToken = data.session?.refresh_token;

Refresh Token

Access tokens expire after 1 hour. Use refresh tokens to get new access tokens:
const { data, error } = await supabase.auth.refreshSession({
  refresh_token: 'YOUR_REFRESH_TOKEN'
});

const newAccessToken = data.session?.access_token;

OAuth Authentication

Authenticate with OAuth providers:

Supported Providers

  • Google
  • Microsoft Azure AD
  • GitHub
  • GitLab

OAuth Flow

// Redirect to OAuth provider
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://opshub.nomark.ai/auth/callback',
    scopes: 'email profile'
  }
});

// Handle callback
const { data: { session }, error: callbackError } = await supabase.auth.getSession();

Service Role Authentication

For administrative operations and server-to-server communication:
The service role key bypasses Row Level Security (RLS). Use only in trusted server environments.
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!, // Service role key
  {
    auth: {
      autoRefreshToken: false,
      persistSession: false
    }
  }
);

// Bypass RLS - use with caution
const { data, error } = await supabase
  .from('investment.portfolios')
  .select('*');

Row Level Security (RLS)

OpsHub uses PostgreSQL Row Level Security to enforce access control:

Role-Based Access

Users are automatically assigned roles:
  • ADMIN - Full system access
  • FUND_MANAGER - Manage funds and strategies
  • PORTFOLIO_MANAGER - Manage portfolios
  • OPERATIONS_LEAD - Operational activities
  • COMPLIANCE_OFFICER - Compliance and audits
  • VIEWER - Read-only access

Team-Based Access

Users can only access data for teams they belong to:
-- Example RLS policy
CREATE POLICY portfolio_team_access ON investment.portfolios
  FOR SELECT
  USING (
    team_id IN (
      SELECT team_id FROM iam.team_members
      WHERE user_id = auth.uid()
    )
  );

Check Your Permissions

// Get current user's roles
const { data: userRoles, error } = await supabase
  .rpc('get_user_roles', { user_uuid: user.id });

// Check specific permission
const { data: hasPermission, error } = await supabase
  .rpc('user_has_permission', {
    user_uuid: user.id,
    permission_code: 'portfolios:write'
  });

Multi-Factor Authentication (MFA)

Enable MFA for enhanced security:

Enroll MFA

// Enroll TOTP MFA
const { data, error } = await supabase.auth.mfa.enroll({
  factorType: 'totp',
  friendlyName: 'My Authenticator App'
});

// Display QR code to user
const qrCode = data?.totp?.qr_code;
const secret = data?.totp?.secret;

Verify MFA

// Challenge MFA
const { data: challengeData, error } = await supabase.auth.mfa.challenge({
  factorId: 'factor-id'
});

// Verify code from authenticator app
const { data: verifyData, error: verifyError } = await supabase.auth.mfa.verify({
  factorId: 'factor-id',
  challengeId: challengeData.id,
  code: '123456'
});

Session Management

Get Current Session

const { data: { session }, error } = await supabase.auth.getSession();

if (session) {
  console.log('User is authenticated:', session.user);
  console.log('Access token:', session.access_token);
}

Sign Out

const { error } = await supabase.auth.signOut();

Listen to Auth Changes

supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    console.log('User signed in:', session?.user);
  } else if (event === 'SIGNED_OUT') {
    console.log('User signed out');
  } else if (event === 'TOKEN_REFRESHED') {
    console.log('Token refreshed:', session?.access_token);
  }
});

Security Best Practices

  • Use environment variables for API keys
  • Never commit credentials to version control
  • Rotate API keys regularly
  • Use different keys for different environments
  • Always use HTTPS for API requests
  • Enable HSTS in your application
  • Validate SSL certificates
  • Respect API rate limits
  • Implement exponential backoff for retries
  • Cache responses when appropriate
  • Implement automatic token refresh
  • Handle 401 errors gracefully
  • Store refresh tokens securely
  • Require MFA for admin accounts
  • Use TOTP or WebAuthn
  • Provide backup codes

Troubleshooting

Common Issues

Cause: Invalid or expired access tokenSolution:
  • Check that you’re including the Authorization header
  • Verify the token hasn’t expired
  • Refresh the token using the refresh token
Cause: Insufficient permissionsSolution:
  • Check your user role and permissions
  • Verify you’re a member of the required team
  • Contact your admin to grant access
Cause: Invalid or expired refresh tokenSolution:
  • Sign in again to get a new session
  • Check refresh token is stored correctly
  • Verify refresh token hasn’t been revoked

Next Steps