Skip to main content

Auth0 Integration

Integrate Auth0 as the identity provider for your marketplace application. This guide uses the Management API v2 for user provisioning and the Authentication API for token acquisition.

Prerequisites

  • Auth0 Tenant: Developer or production tenant
  • Regular Web Application with:
    • Client ID and Client Secret
    • "Password" grant enabled (Application > Advanced > Grant Types)
    • Appropriate API audience configured
  • Database Connection: Username-Password-Authentication (default) or custom
  • Roles: Create a gw-user role under User Management > Roles
  • Login Action: Deploy an Action to inject custom claims into tokens

Auth0 Tenant Setup

  1. Create a Regular Web Application:

    Application Type: Regular Web Application
    Name: GW Marketplace Integration
    Grant Types: Authorization Code, Client Credentials, Password
  2. Create a gw-user Role:

    • User Management > Roles > Create Role
    • Name: gw-user
  3. Deploy a Login Action (Login flow) to add custom claims to tokens:

    exports.onExecutePostLogin = async (event, api) => {
    const namespace = 'https://gw-marketplace/';
    api.accessToken.setCustomClaim(namespace + 'roles', event.authorization?.roles || []);
    api.accessToken.setCustomClaim(namespace + 'orgId', event.user.app_metadata?.orgId || '');
    api.accessToken.setCustomClaim(namespace + 'gwUserId', event.user.app_metadata?.gwUserId || '');
    api.accessToken.setCustomClaim(
    namespace + 'gwApplicationId',
    event.user.app_metadata?.gwApplicationId || ''
    );
    };
  4. Grant Management API Permissions to your application:

    • APIs > Auth0 Management API > Machine to Machine Applications
    • Authorize your application with scopes: read:users, create:users, update:users, read:roles, create:role_members

Environment Variables

# Auth0 Tenant
AUTH0_DOMAIN=your-tenant.us.auth0.com

# Application Credentials
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret

# Database Connection (default: Username-Password-Authentication)
AUTH0_CONNECTION=Username-Password-Authentication

# API Audience (optional -- defaults to Management API)
AUTH0_AUDIENCE=https://your-api.example.com

# Marketplace Configuration
MARKETPLACE_APP_ID=your-registered-app-id

Exchange Flow

sequenceDiagram
participant SDK as Provider SDK
participant Backend as Your Backend
participant Auth0 as Auth0

SDK->>Backend: POST /auth/exchange (Bearer marketplace-jwt)
Backend->>Backend: Validate JWT (signature, iss, applicationId, exp)
Backend->>Auth0: POST /oauth/token (client_credentials)
Auth0-->>Backend: Management API token
Backend->>Auth0: GET /api/v2/users?q=app_metadata.gwUserId:"..."
alt User exists
Auth0-->>Backend: User record
Backend->>Auth0: PATCH /api/v2/users/{id} (update app_metadata)
else User not found
Backend->>Auth0: POST /api/v2/users (create with password)
Auth0-->>Backend: Created user
Backend->>Auth0: POST /api/v2/users/{id}/roles (assign gw-user)
end
Backend->>Auth0: POST /oauth/token (password grant)
Auth0-->>Backend: access_token + refresh_token
Backend-->>SDK: Tokens + user info

Backend Implementation

import { createHash } from 'crypto';

const AUTH0_BASE_URL = `https://${process.env.AUTH0_DOMAIN}`;
const CLAIM_NAMESPACE = 'https://gw-marketplace/';

// Deterministic password derivation
function derivedPassword(userId: string): string {
const hash = createHash('sha256')
.update(`${userId}${process.env.AUTH0_CLIENT_SECRET}`)
.digest('hex');
// Auth0 requires: 8+ chars, 1 upper, 1 lower, 1 number, 1 special
return `Gw!${hash.slice(0, 29)}`;
}

// Obtain Management API token (cached until expiry)
let mgmtToken: string | null = null;
let mgmtTokenExpiry = 0;

async function getManagementToken(): Promise<string> {
if (mgmtToken && Date.now() < mgmtTokenExpiry - 10_000) {
return mgmtToken;
}

const res = await fetch(`${AUTH0_BASE_URL}/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.AUTH0_CLIENT_ID,
client_secret: process.env.AUTH0_CLIENT_SECRET,
audience: `${AUTH0_BASE_URL}/api/v2/`,
}),
});

if (!res.ok) throw new Error(`Management token failed: ${res.status}`);
const data = await res.json();
mgmtToken = data.access_token;
mgmtTokenExpiry = Date.now() + data.expires_in * 1000;
return data.access_token;
}

// Search for existing user by gwUserId in app_metadata
async function findUser(gwUserId: string) {
const token = await getManagementToken();
const q = encodeURIComponent(`app_metadata.gwUserId:"${gwUserId}"`);

const res = await fetch(`${AUTH0_BASE_URL}/api/v2/users?q=${q}&search_engine=v3`, {
headers: { Authorization: `Bearer ${token}` },
});

if (!res.ok) throw new Error(`User search failed: ${res.status}`);
const users = await res.json();
return users.length > 0 ? users[0] : null;
}

// Create user with password and app_metadata
async function createUser(claims: any) {
const token = await getManagementToken();
const email = claims.email ?? `${claims.userId}@gw-marketplace.local`;
const password = derivedPassword(claims.userId);

const res = await fetch(`${AUTH0_BASE_URL}/api/v2/users`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
connection: process.env.AUTH0_CONNECTION ?? 'Username-Password-Authentication',
email,
password,
email_verified: true,
app_metadata: {
gwUserId: claims.userId,
orgId: claims.orgId,
gwApplicationId: claims.applicationId,
},
}),
});

if (!res.ok) throw new Error(`Create user failed: ${res.status}`);
return res.json();
}

// Assign a role to a user
async function assignRole(userId: string, roleName: string) {
const token = await getManagementToken();

// Find role by name
const rolesRes = await fetch(
`${AUTH0_BASE_URL}/api/v2/roles?name_filter=${encodeURIComponent(roleName)}`,
{ headers: { Authorization: `Bearer ${token}` } },
);
const roles = await rolesRes.json();
if (roles.length === 0) return;

// Assign role to user
await fetch(`${AUTH0_BASE_URL}/api/v2/users/${encodeURIComponent(userId)}/roles`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ roles: [roles[0].id] }),
});
}

// Obtain user tokens via Resource Owner Password grant
async function getUserTokens(email: string, password: string) {
const body: Record<string, string> = {
grant_type: 'password',
client_id: process.env.AUTH0_CLIENT_ID!,
client_secret: process.env.AUTH0_CLIENT_SECRET!,
username: email,
password,
scope: 'openid profile email offline_access',
};

// Set audience if configured for a custom API
if (process.env.AUTH0_AUDIENCE) {
body.audience = process.env.AUTH0_AUDIENCE;
}

const res = await fetch(`${AUTH0_BASE_URL}/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});

if (!res.ok) throw new Error(`Token grant failed: ${res.status}`);
return res.json();
}

Token Structure

Auth0 access tokens contain custom claims under a namespace:

{
"iss": "https://your-tenant.us.auth0.com/",
"exp": 1714000000,
"sub": "auth0|abc123def456",
"email": "user@example.com",
"nickname": "gw-user-abc123",
"https://gw-marketplace/roles": ["gw-user"],
"https://gw-marketplace/orgId": "org-456",
"https://gw-marketplace/gwUserId": "user-abc123",
"https://gw-marketplace/gwApplicationId": "app-789"
}

Access custom claims using the namespace prefix:

function decodeAccessToken(accessToken: string) {
const payload = JSON.parse(
Buffer.from(accessToken.split('.')[1], 'base64url').toString()
);
const ns = 'https://gw-marketplace/';

return {
roles: payload[`${ns}roles`] ?? [],
orgId: payload[`${ns}orgId`] ?? null,
gwUserId: payload[`${ns}gwUserId`] ?? null,
gwApplicationId: payload[`${ns}gwApplicationId`] ?? null,
};
}

Session Revocation

Revoke sessions using the token revocation endpoint:

async function revokeSession(refreshToken: string) {
await fetch(`${AUTH0_BASE_URL}/oauth/revoke`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.AUTH0_CLIENT_ID,
client_secret: process.env.AUTH0_CLIENT_SECRET,
token: refreshToken,
}),
});
}

Management API v2 Reference

Base URL: https://{AUTH0_DOMAIN}/api/v2

OperationMethodEndpoint
Search UsersGET/users?q={query}&search_engine=v3
Create UserPOST/users
Update UserPATCH/users/{id}
Get User RolesGET/users/{id}/roles
Assign RolesPOST/users/{id}/roles
List RolesGET/roles?name_filter={name}

Search Query Syntax

Auth0 uses Lucene query syntax for user searches:

# Search by app_metadata field
q=app_metadata.gwUserId:"user-abc123"

# Search by email
q=email:"user@example.com"

# Combined search
q=app_metadata.gwUserId:"user-abc123" OR email:"user@example.com"

Common Gotchas

  1. Password policy violations: Auth0 requires at least 8 characters with uppercase, lowercase, number, and special character. The Gw! prefix in the derived password satisfies these requirements.

  2. Management API rate limits: Auth0 enforces rate limits on the Management API. Implement exponential backoff for 429 responses. Cache the management token to avoid repeated token requests.

  3. Namespace required for custom claims: Auth0 requires custom claims in access tokens to be namespaced with a URL (e.g., https://gw-marketplace/). Without the namespace, custom claims are silently dropped.

  4. Password grant must be explicitly enabled: The Resource Owner Password grant is disabled by default. Enable it under Application > Advanced > Grant Types.

  5. Connection name matters: The connection field in user creation must match your database connection name exactly. The default is Username-Password-Authentication.

  6. search_engine=v3 is required: User searches without search_engine=v3 use the legacy search engine which does not support app_metadata queries.

Additional Resources