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

Agent API

Connect to the AI agent via WebSocket

Data API

Manage portfolios and holdings

Validation API

Create and manage validation rules

Workflows

Orchestrate complex workflows