> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moduluslabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Learn how to authenticate with JWT Bearer Tokens

## Overview

The Onboarding API uses **JWT (JSON Web Token) Bearer Token authentication**. Unlike the QR API which uses HTTP Basic Auth, the Onboarding API requires you to generate and sign JWT tokens with your secret key.

<Info>
  JWT tokens ensure that requests are authentic and haven't been tampered with. The secret key is private and should never be exposed publicly.
</Info>

**Prerequisite:**
Before generating JWT tokens, you must first create an account
using the [Create Account API](/api-reference/onboarding/create-account) to obtain
your Account ID and email address. These credentials are required for the JWT payload.

## How JWT Authentication Works

<Steps>
  <Step title="Generate JWT Payload">
    Create a JSON payload containing the account ID and email address
  </Step>

  <Step title="Sign with Secret Key">
    Sign the payload with your secret key using the HS256 algorithm
  </Step>

  <Step title="Include in Authorization Header">
    Send the JWT token in the `Authorization: Bearer {token}` header
  </Step>

  <Step title="Server Verification">
    Modulus Labs verifies the token signature using the same secret key
  </Step>
</Steps>

## Authentication Flow

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant API as Modulus Labs API

    Client->>Client: Generate JWT with secret key
    Client->>API: POST /v2/onboard (Authorization: Bearer {JWT})
    API->>API: Verify JWT signature
    alt Valid Token
        API->>Client: 200 OK + Response Data
    else Invalid Token
        API->>Client: 401 Unauthorized
    end
```

## Secret Key

Your secret key will be provided by Modulus Labs. Store it securely:

<Warning>
  Never commit your secret key to version control or expose it in client-side code. Always store it in environment variables or secure secret management systems.
</Warning>

<CodeGroup>
  ```bash .env theme={null}
  MODULUS_ONBOARDING_SECRET_KEY=your_secret_key_here
  ```

  ```javascript Node.js theme={null}
  const SECRET_KEY = process.env.MODULUS_ONBOARDING_SECRET_KEY;
  ```

  ```python Python theme={null}
  import os
  SECRET_KEY = os.getenv('MODULUS_ONBOARDING_SECRET_KEY')
  ```
</CodeGroup>

## JWT Payload Structure

The JWT payload must contain these required fields:

| Field   | Type   | Required | Description                                 |
| ------- | ------ | -------- | ------------------------------------------- |
| `sub`   | number | Yes      | Account ID received from Create Account API |
| `email` | string | Yes      | Email address registered to the account     |

### Example Payload

```json theme={null}
{
  "sub": 1,
  "email": "merchant@gmail.com"
}
```

<ParamField body="sub" type="number" required>
  The Account ID received from the Create Account API. This identifies which merchant account is making the request.

  **Example:** `1`, `12345`
</ParamField>

<ParamField body="email" type="string" required>
  The email address registered to the merchant account. Must match the email used during account creation.

  **Format:** Valid email address

  **Example:** `"merchant@gmail.com"`, `"business@company.com"`
</ParamField>

## Generating JWT Tokens

Here's how to generate JWT tokens in different languages:

<CodeGroup>
  ```javascript Node.js theme={null}
  const jwt = require('jsonwebtoken');

  const SECRET_KEY = process.env.MODULUS_ONBOARDING_SECRET_KEY;

  function generateToken(accountId, email) {
    const payload = {
      sub: accountId,
      email: email
    };

    const token = jwt.sign(payload, SECRET_KEY, {
      algorithm: 'HS256'
    });

    return token;
  }

  // Usage
  const accountId = 1;
  const email = 'merchant@gmail.com';
  const token = generateToken(accountId, email);

  console.log('JWT Token:', token);
  // Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsImVtYWlsIjoibWVyY2hhbnRAZ21haWwuY29tIiwiaWF0IjoxNjQwOTk1MjAwfQ.xyz123...
  ```

  ```python Python theme={null}
  import jwt
  import os
  from datetime import datetime, timedelta

  SECRET_KEY = os.getenv('MODULUS_ONBOARDING_SECRET_KEY')

  def generate_token(account_id, email):
      payload = {
          'sub': account_id,
          'email': email
      }

      token = jwt.encode(
          payload,
          SECRET_KEY,
          algorithm='HS256'
      )

      return token

  # Usage
  account_id = 1
  email = 'merchant@gmail.com'
  token = generate_token(account_id, email)

  print(f'JWT Token: {token}')
  ```

  ```php PHP theme={null}
  <?php
  require 'vendor/autoload.php';
  use Firebase\JWT\JWT;

  $secretKey = getenv('MODULUS_ONBOARDING_SECRET_KEY');

  function generateToken($accountId, $email) {
      global $secretKey;

      $payload = [
          'sub' => $accountId,
          'email' => $email
      ];

      $token = JWT::encode($payload, $secretKey, 'HS256');

      return $token;
  }

  // Usage
  $accountId = 1;
  $email = 'merchant@gmail.com';
  $token = generateToken($accountId, $email);

  echo "JWT Token: " . $token . "\n";
  ?>
  ```

  ```ruby Ruby theme={null}
  require 'jwt'

  SECRET_KEY = ENV['MODULUS_ONBOARDING_SECRET_KEY']

  def generate_token(account_id, email)
    payload = {
      sub: account_id,
      email: email
    }

    token = JWT.encode(payload, SECRET_KEY, 'HS256')

    token
  end

  # Usage
  account_id = 1
  email = 'merchant@gmail.com'
  token = generate_token(account_id, email)

  puts "JWT Token: #{token}"
  ```

  ```go Go theme={null}
  package main

  import (
      "fmt"
      "os"
      "github.com/golang-jwt/jwt/v5"
  )

  var secretKey = os.Getenv("MODULUS_ONBOARDING_SECRET_KEY")

  type Claims struct {
      Sub   int    `json:"sub"`
      Email string `json:"email"`
      jwt.RegisteredClaims
  }

  func generateToken(accountID int, email string) (string, error) {
      claims := Claims{
          Sub:   accountID,
          Email: email,
      }

      token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
      tokenString, err := token.SignedString([]byte(secretKey))

      return tokenString, err
  }

  func main() {
      accountID := 1
      email := "merchant@gmail.com"

      token, err := generateToken(accountID, email)
      if err != nil {
          panic(err)
      }

      fmt.Printf("JWT Token: %s\n", token)
  }
  ```
</CodeGroup>

## Required Libraries

Install the necessary JWT libraries for your language:

<Tabs>
  <Tab title="Node.js">
    ```bash theme={null}
    npm install jsonwebtoken
    ```
  </Tab>

  <Tab title="Python">
    ```bash theme={null}
    pip install pyjwt
    ```
  </Tab>

  <Tab title="PHP">
    ```bash theme={null}
    composer require firebase/php-jwt
    ```
  </Tab>

  <Tab title="Ruby">
    ```bash theme={null}
    gem install jwt
    ```
  </Tab>

  <Tab title="Go">
    ```bash theme={null}
    go get -u github.com/golang-jwt/jwt/v5
    ```
  </Tab>
</Tabs>

## Making Authenticated Requests

Include the JWT token in the `Authorization` header with the `Bearer` prefix:

<CodeGroup>
  ```javascript Node.js theme={null}
  const axios = require('axios');

  async function onboardMerchant(token, merchantData) {
    const response = await axios.post(
      'https://kyc.sbx.moduluslabs.io/v2/onboard',
      merchantData,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    return response.data;
  }

  // Usage
  const token = generateToken(1, 'merchant@gmail.com');
  const result = await onboardMerchant(token, merchantData);
  ```

  ```python Python theme={null}
  import requests

  def onboard_merchant(token, merchant_data):
      response = requests.post(
          'https://kyc.sbx.moduluslabs.io/v2/onboard',
          json=merchant_data,
          headers={
              'Authorization': f'Bearer {token}',
              'Content-Type': 'application/json'
          }
      )

      response.raise_for_status()
      return response.json()

  # Usage
  token = generate_token(1, 'merchant@gmail.com')
  result = onboard_merchant(token, merchant_data)
  ```

  ```bash cURL theme={null}
  curl -X POST https://kyc.sbx.moduluslabs.io/v2/onboard \
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
    -H "Content-Type: application/json" \
    -d '{
      "businessType": "STARTER",
      "merchantName": "My Business",
      ...
    }'
  ```
</CodeGroup>

## Token Best Practices

<CardGroup cols={2}>
  <Card title="Generate Per Request" icon="arrows-rotate">
    Generate a new token for each API request rather than reusing old tokens
  </Card>

  <Card title="Secure Storage" icon="vault">
    Store secret keys in environment variables or secret management systems
  </Card>

  <Card title="HTTPS Only" icon="lock">
    Always send tokens over HTTPS to prevent interception
  </Card>

  <Card title="Server-Side Only" icon="server">
    Never generate or store tokens in client-side JavaScript
  </Card>

  <Card title="Validate Inputs" icon="shield-check">
    Validate accountId and email before generating tokens
  </Card>

  <Card title="Handle Errors" icon="triangle-exclamation">
    Implement proper error handling for token generation failures
  </Card>
</CardGroup>

## Token Expiration

<Note>
  The Onboarding API JWT tokens **do not require an expiration time (`exp` claim)**. However, you can add one for additional security if desired.
</Note>

### Optional: Adding Expiration

If you want to add token expiration for extra security:

<CodeGroup>
  ```javascript Node.js theme={null}
  const jwt = require('jsonwebtoken');

  function generateTokenWithExpiry(accountId, email, expiresIn = '1h') {
    const payload = {
      sub: accountId,
      email: email
    };

    const token = jwt.sign(payload, SECRET_KEY, {
      algorithm: 'HS256',
      expiresIn: expiresIn  // '1h', '24h', '7d', etc.
    });

    return token;
  }

  // Token expires in 1 hour
  const token = generateTokenWithExpiry(1, 'merchant@gmail.com', '1h');
  ```

  ```python Python theme={null}
  import jwt
  from datetime import datetime, timedelta

  def generate_token_with_expiry(account_id, email, expires_in_hours=1):
      payload = {
          'sub': account_id,
          'email': email,
          'exp': datetime.utcnow() + timedelta(hours=expires_in_hours)
      }

      token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
      return token

  # Token expires in 1 hour
  token = generate_token_with_expiry(1, 'merchant@gmail.com', 1)
  ```
</CodeGroup>

## Error Responses

Authentication errors return a standard error format:

### 401 Unauthorized

```json theme={null}
{
  "code": "20000001",
  "error": "Invalid or expired JWT token",
  "referenceNumber": "abc123-def456-ghi789"
}
```

<AccordionGroup>
  <Accordion title="Invalid Token" icon="circle-xmark">
    **Cause:** Token signature doesn't match or token is malformed

    **Solution:**

    * Verify you're using the correct secret key
    * Ensure the token is properly formatted
    * Check for any whitespace in the token string
  </Accordion>

  <Accordion title="Expired Token" icon="clock">
    **Cause:** Token has expired (if you added an `exp` claim)

    **Solution:**

    * Generate a new token
    * Increase expiration time if needed
  </Accordion>

  <Accordion title="Missing Token" icon="ban">
    **Cause:** Authorization header is missing or doesn't contain Bearer token

    **Solution:**

    * Ensure header format is: `Authorization: Bearer {token}`
    * Check that the token is not empty
  </Accordion>

  <Accordion title="Invalid Credentials" icon="user-xmark">
    **Cause:** Account ID or email in payload doesn't match account records

    **Solution:**

    * Verify the account ID is correct
    * Ensure email matches the registered account email
  </Accordion>
</AccordionGroup>

## Security Considerations

<Warning>
  **Never expose your secret key publicly**. Anyone with your secret key can generate valid tokens and access your API.
</Warning>

### Security Checklist

<Steps>
  <Step title="Store Keys Securely">
    Use environment variables, AWS Secrets Manager, or similar secure storage
  </Step>

  <Step title="Use HTTPS">
    All API requests must be made over HTTPS (HTTP will fail)
  </Step>

  <Step title="Server-Side Generation">
    Always generate JWT tokens on your backend server, never in browser JavaScript
  </Step>

  <Step title="Rotate Keys">
    Implement a key rotation strategy if your secret key is compromised
  </Step>

  <Step title="Monitor Usage">
    Log and monitor API usage to detect suspicious activity
  </Step>
</Steps>

## Testing Authentication

Test your JWT generation before making actual API calls:

<CodeGroup>
  ```javascript Node.js theme={null}
  const jwt = require('jsonwebtoken');

  // Generate token
  const token = generateToken(1, 'merchant@gmail.com');
  console.log('Generated Token:', token);

  // Decode token (for testing only)
  const decoded = jwt.decode(token);
  console.log('Decoded Payload:', decoded);

  // Verify token (simulates server verification)
  try {
    const verified = jwt.verify(token, SECRET_KEY);
    console.log(' Token is valid:', verified);
  } catch (error) {
    console.error(' Token verification failed:', error.message);
  }
  ```

  ```python Python theme={null}
  import jwt

  # Generate token
  token = generate_token(1, 'merchant@gmail.com')
  print(f'Generated Token: {token}')

  # Decode token (for testing only)
  decoded = jwt.decode(token, options={"verify_signature": False})
  print(f'Decoded Payload: {decoded}')

  # Verify token (simulates server verification)
  try:
      verified = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
      print(f' Token is valid: {verified}')
  except jwt.InvalidTokenError as e:
      print(f' Token verification failed: {str(e)}')
  ```
</CodeGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Onboard Merchant API" icon="user-plus" href="/api-reference/onboarding/onboard-merchant">
    Use your JWT token to onboard merchants
  </Card>

  <Card title="Enums Reference" icon="list" href="/docs/onboarding/enums">
    All valid values for business types and banks
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/docs/errors">
    Handle authentication errors properly
  </Card>

  <Card title="Introduction" icon="book" href="/docs/onboarding/introduction">
    Back to Onboarding API overview
  </Card>
</CardGroup>
