Authentication


Authentication

The Lumanu Partner API supports two authentication methods to accommodate different integration scenarios. Choose the appropriate method based on whether your integration needs to manage workspaces autonomously or act on behalf of users.

Authentication Methods Overview

Client Credentials Flow

Best for: Server-to-server integrations where your application manages workspaces it owns

Use this flow when:

  • Your platform creates and manages workspaces on behalf of your business
  • You need programmatic access to automate payment workflows
  • Your integration operates independently without user interaction
  • You're implementing the Platform-Mediated money flow (your platform is in the money flow)

OAuth Authorization Code Flow

Best for: Third-party integrations that act on behalf of users

Use this flow when:

  • Users need to grant your application access to their Lumanu workspaces
  • You're building an integration that users install or authorize
  • You need to perform actions on behalf of specific users
  • You're implementing the Direct Client flow (clients manage their own workspaces)

Client Credentials Flow

The Client Credentials flow provides secure server-to-server authentication for applications that manage their own workspaces.

Getting Access Tokens

  1. You will receive a Client ID and Client Secret from Lumanu
  2. Exchange these credentials for an access token by making a POST request to the appropriate authentication endpoint for your environment:
POST https://{environment-auth-domain}/oauth/token
Content-Type: application/json

{
    "client_id": "{your_client_id}",
    "client_secret": "{your_client_secret}",
    "audience": "{api-base-domain}",
    "grant_type": "client_credentials"
}

Environment-specific values:

EnvironmentAuth DomainAPI Base Domain
Sandboxauth-staging.lumanu.comhttps://lumanu-demo.hasura.app/v1/graphql
Productionauth.lumanu.comhttps://lumanu-prod.hasura.app/v1/graphql

Example Response:

{
  "access_token": "eyJhbGc...",
  "token_type": "Bearer",
  "expires_in": 86400
}

Using Access Tokens

Include the access token in the Authorization header of all API requests:

Authorization: Bearer eyJhbGc...

Token Lifecycle

  • Access tokens are valid for 24 hours (86400 seconds)
  • Request a new token when the current token expires
  • Store tokens securely and never expose them in client-side code
  • Do not share tokens between different integrations

Example Use Case

Your payment automation platform creates workspaces for internal use and programmatically manages all payment operations:

// Get access token
const response = await fetch('https://auth-staging.lumanu.com/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    client_id: process.env.LUMANU_CLIENT_ID,
    client_secret: process.env.LUMANU_CLIENT_SECRET,
    audience: 'https://lumanu-demo.hasura.app/v1/graphql',
    grant_type: 'client_credentials',
  }),
});

const { access_token } = await response.json();

// Use token to create a payable
await fetch('https://api.demo.lumanu.link/api/rest/payable', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${access_token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    workspace_id: 'your-workspace-id',
    payee_email: '[email protected]',
    amount: 150000,
    description: 'Campaign payment',
  }),
});

OAuth Authorization Code Flow

The OAuth Authorization Code flow enables third-party applications to act on behalf of users with their explicit consent.

Flow Diagram

sequenceDiagram
    participant User as User's Browser
    participant App as Your Application
    participant Lumanu as Lumanu OAuth
    participant Auth0 as Auth0 Login

    User->>App: 1. User clicks "Connect Lumanu"
    App->>User: 2. Redirect to /api/oauth/authorize<br/>?client_id=...<br/>&redirect_uri=https://yourapp.com/callback<br/>&response_type=code<br/>&scope=openid profile email<br/>&state=random_string
    User->>Lumanu: 3. GET /api/oauth/authorize
    Lumanu->>Auth0: 4. Redirect to Auth0<br/>(with audience automatically added)
    Auth0->>User: 5. Display login page
    User->>Auth0: 6. User authenticates & authorizes
    Auth0->>User: 7. Redirect to callback<br/>?code=auth_code&state=random_string
    User->>App: 8. GET /oauth/callback?code=...&state=...
    App->>Auth0: 9. POST /oauth/token<br/>Exchange code for access token
    Auth0->>App: 10. Return access_token, refresh_token
    App->>App: 11. Store tokens securely
    App->>Lumanu: 12. Make API calls with access token
    Lumanu->>App: 13. API responses
    App->>User: 14. Display success & return to app

Implementation Steps

Step 1: Register Your OAuth Application

Contact Lumanu to register your application and receive:

  • Client ID: Public identifier for your application
  • Client Secret: Secret credential (keep secure, never expose client-side)
  • Allowed Redirect URIs: Whitelisted callback URLs

Step 2: Initiate Authorization

Redirect users to the Lumanu OAuth authorization endpoint with the following parameters:

Authorization URL:

GET https://api.{environment}.lumanu.link/api/oauth/authorize

Required Query Parameters:

ParameterDescriptionExample
client_idYour OAuth client IDabc123xyz
redirect_uriYour callback URL (must be pre-registered)https://yourapp.com/oauth/callback
response_typeMust be code for authorization code flowcode
stateRandom string for CSRF protectionrandom_string_123

Optional Parameters:

ParameterDescriptionExample
scopeSpace-separated scopes (defaults to openid profile email)openid profile email
audienceAPI audience (auto-populated if not provided)https://lumanu-demo.hasura.app/v1/graphql

Example Authorization URL:

https://api.demo.lumanu.link/api/oauth/authorize
  ?client_id=your_client_id
  &redirect_uri=https://yourapp.com/oauth/callback
  &response_type=code
  &state=random_string_for_csrf_protection
  &scope=openid profile email

Step 3: Handle the Callback

After the user authorizes your application, they'll be redirected to your redirect_uri with an authorization code:

https://yourapp.com/oauth/callback
  ?code=AUTHORIZATION_CODE
  &state=random_string_for_csrf_protection

Important: Always verify the state parameter matches what you sent to prevent CSRF attacks.

Step 4: Exchange Code for Access Token

Exchange the authorization code for an access token by making a POST request to Auth0's token endpoint:

POST https://{environment-auth-domain}/oauth/token
Content-Type: application/json

{
    "client_id": "{your_client_id}",
    "client_secret": "{your_client_secret}",
    "code": "{authorization_code}",
    "grant_type": "authorization_code",
    "redirect_uri": "{same_redirect_uri_from_step_2}"
}

Environment-specific Auth Domains:

  • Sandbox: auth-staging.lumanu.com
  • Production: auth.lumanu.com

Example Response:

{
  "access_token": "eyJhbGc...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "refresh_token": "v1.abc...",
  "id_token": "eyJhbGc..."
}

Step 5: Make Authenticated API Requests

Use the access token to make API requests on behalf of the user:

Authorization: Bearer eyJhbGc...

Token Management

Access Tokens:

  • Valid for 24 hours (86400 seconds)
  • Should be stored securely (encrypted database or secure session)
  • Never expose in client-side code or URLs

Refresh Tokens:

  • Can be used to obtain new access tokens without user interaction
  • Valid for extended periods (check token response)
  • Store with same security as access tokens

Refreshing Tokens:

POST https://{environment-auth-domain}/oauth/token
Content-Type: application/json

{
    "client_id": "{your_client_id}",
    "client_secret": "{your_client_secret}",
    "grant_type": "refresh_token",
    "refresh_token": "{refresh_token}"
}

Example Implementation

Node.js/Express Example:

const express = require('express');
const crypto = require('crypto');
const app = express();

// Configuration
const LUMANU_CLIENT_ID = process.env.LUMANU_CLIENT_ID;
const LUMANU_CLIENT_SECRET = process.env.LUMANU_CLIENT_SECRET;
const LUMANU_AUTH_URL = 'https://api.demo.lumanu.link/api/oauth/authorize';
const AUTH0_DOMAIN = 'auth-staging.lumanu.com';
const REDIRECT_URI = 'https://yourapp.com/oauth/callback';

// Step 1: Initiate OAuth flow
app.get('/connect/lumanu', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex');

  // Store state in session for verification
  req.session.oauthState = state;

  const authUrl = new URL(LUMANU_AUTH_URL);
  authUrl.searchParams.set('client_id', LUMANU_CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('state', state);
  authUrl.searchParams.set('scope', 'openid profile email');

  res.redirect(authUrl.toString());
});

// Step 2: Handle OAuth callback
app.get('/oauth/callback', async (req, res) => {
  const { code, state } = req.query;

  // Verify state to prevent CSRF
  if (state !== req.session.oauthState) {
    return res.status(403).send('Invalid state parameter');
  }

  try {
    // Exchange code for access token
    const tokenResponse = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        client_id: LUMANU_CLIENT_ID,
        client_secret: LUMANU_CLIENT_SECRET,
        code: code,
        grant_type: 'authorization_code',
        redirect_uri: REDIRECT_URI,
      }),
    });

    const tokens = await tokenResponse.json();

    // Store tokens securely (encrypted in database)
    await storeUserTokens(req.user.id, {
      access_token: tokens.access_token,
      refresh_token: tokens.refresh_token,
      expires_at: Date.now() + tokens.expires_in * 1000,
    });

    // Get user's workspaces
    const workspacesResponse = await fetch(
      'https://api.demo.lumanu.link/api/rest/user/current/workspaces',
      {
        headers: {
          Authorization: `Bearer ${tokens.access_token}`,
        },
      }
    );

    const workspaces = await workspacesResponse.json();

    res.redirect('/dashboard?connected=success');
  } catch (error) {
    console.error('OAuth error:', error);
    res.status(500).send('Authentication failed');
  }
});

// Step 3: Make API calls with stored token
app.post('/create-payment', async (req, res) => {
  const tokens = await getUserTokens(req.user.id);

  // Check if token needs refresh
  if (Date.now() >= tokens.expires_at) {
    tokens = await refreshAccessToken(tokens.refresh_token);
  }

  const response = await fetch(
    'https://api.demo.lumanu.link/api/rest/payable',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${tokens.access_token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        workspace_id: req.body.workspace_id,
        payee_email: req.body.payee_email,
        amount: req.body.amount,
        description: req.body.description,
      }),
    }
  );

  const payable = await response.json();
  res.json(payable);
});

async function refreshAccessToken(refreshToken) {
  const response = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      client_id: LUMANU_CLIENT_ID,
      client_secret: LUMANU_CLIENT_SECRET,
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
    }),
  });

  const tokens = await response.json();

  // Update stored tokens
  await storeUserTokens(userId, {
    access_token: tokens.access_token,
    refresh_token: tokens.refresh_token || refreshToken,
    expires_at: Date.now() + tokens.expires_in * 1000,
  });

  return tokens;
}

Testing OAuth Flow

Lumanu provides an OAuth test server to help you test the OAuth flow during development.

Starting the test server:

cd oauth-test-server
doppler run -- npm run dev

The test server provides a simple interface to:

  1. Initiate the OAuth flow
  2. Complete authentication
  3. Extract and display access tokens
  4. Test API calls with the token

Note: The test server is only for development and testing purposes.


Security Best Practices

For Both Methods

  1. Never expose credentials client-side

    • Client secrets and access tokens should only be used in server-side code
    • Never include them in frontend JavaScript, mobile apps, or version control
  2. Use HTTPS only

    • All authentication requests must use HTTPS
    • Redirect URIs must use HTTPS (except localhost for testing)
  3. Store tokens securely

    • Encrypt tokens at rest in your database
    • Use secure session management
    • Never log tokens or include them in error messages
  4. Implement token rotation

    • Refresh access tokens before they expire
    • Invalidate tokens when users disconnect your integration

OAuth-Specific

  1. Validate state parameter

    • Always verify the state parameter matches to prevent CSRF attacks
    • Use cryptographically random values
  2. Validate redirect_uri

    • Ensure the redirect URI exactly matches what was registered
    • Use strict string comparison
  3. Handle errors gracefully

    • Check for error parameters in callback (?error=access_denied)
    • Provide clear error messages to users
  4. Scope management

    • Request only the scopes you need
    • Clearly explain to users what permissions you're requesting

Choosing the Right Method

ScenarioRecommended MethodReason
Users need to connect their existing Lumanu accountsOAuthUsers retain control and can revoke access
Building a marketplace integrationOAuthMultiple users with different permissions
Automated payment processing for your businessClient CredentialsNo user interaction needed
Third-party app installed by usersOAuthUser explicitly grants access
Internal tools and scriptsClient CredentialsYour organization owns the integration

API Endpoints for User Information

When using OAuth (acting on behalf of a user), you can access user-specific information:

Get Current User

GET /api/rest/user/current
Authorization: Bearer {user_access_token}

Response:

{
  "id": "user-uuid",
  "first_name": "John",
  "last_name": "Doe",
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-01T00:00:00Z"
}

Get User's Workspaces

GET /api/rest/user/current/workspaces
Authorization: Bearer {user_access_token}

Response:

{
  "data": [
    {
      "id": "workspace-uuid",
      "display_name": "Acme Corp",
      "role": {
        "id": "role-uuid",
        "display_name": "Admin"
      }
    }
  ]
}

These endpoints are only accessible with user access tokens obtained through OAuth, not with client credentials tokens.


Need Help?

If you have questions about authentication or need help implementing OAuth:

  1. Check the API Reference for detailed endpoint documentation
  2. Review the Environments guide for environment-specific URLs
  3. Contact Lumanu support for OAuth application registration
  4. Use the oauth-test-server for development and testing