Skip to main content

Prerequisites

Create Passport Client

Set up your Passport client in Immutable Hub to get the credentials needed for authentication.

Create Passport Client

Step-by-step guide to creating a Passport client, configuring redirect URIs, and getting your Client ID
You’ll need:
  • Client ID from your Passport client in Hub
  • Publishable Key from your Hub project settings
These values are required to initialize Passport in the next section.

Passport Credentials Reference

FieldTypeDescription
Client IDProvided by HubUnique identifier for your application. Copy from Hub and use in SDK initialization.
Publishable KeyProvided by HubPublic key safe for client-side code. Copy from Hub project settings.
Application TypeYou configureSelect Web for TypeScript/web apps, Native for Unity/Unreal games.
Application NameYou configureIdentifier for your project inside Passport (e.g., “My Game”).
Redirect URIsYou configureWhere users land after successful authentication. Must exactly match redirectUri in your code. Examples: http://localhost:3000/redirect (web), mygame://callback (native).
Logout URIsYou configureWhere users land after logout. Must exactly match logoutRedirectUri in your code. Examples: http://localhost:3000/logout (web), mygame://logout (native).
Passport uses OpenID Connect (OIDC). Redirect URIs must be exact matches—wildcards aren’t supported for security reasons. Register multiple URIs for different environments (localhost, staging, production).
For complete details on client configuration, see Passport Clients in Hub.

Installation

npm install @imtbl/sdk
# or
yarn add @imtbl/sdk
# or
pnpm add @imtbl/sdk

Initialize Passport

import { Auth, AuthConfiguration } from '@imtbl/auth';
import { Environment } from '@imtbl/config';

export const auth = new Auth(new AuthConfiguration({
  environment: Environment.SANDBOX,
  clientId: process.env.NEXT_PUBLIC_CLIENT_ID || 'YOUR_CLIENT_ID',
  redirectUri: 'http://localhost:3000/redirect',
  logoutRedirectUri: 'http://localhost:3000/logout',
  logoutMode: 'redirect', // or 'silent' - see Logout section for details
  audience: 'platform_api',
  scope: 'openid offline_access email transact',
}));

Scopes

ScopeRequiredDescription
openid✅ YesReturns user ID (sub claim)
offline_access✅ YesEnables refresh tokens for persistent sessions
emailOptionalAccess to user’s email address
transactOptionalPermission to sign transactions

Login

Headless Login

For a more streamlined user experience, the standard Passport login prompt can be bypassed by providing the directLoginOptions parameter to the login method. This allows you to render a customised authentication prompt within your own application.Once an authentication option (email, Google, Apple, or Facebook) is passed to the login method, a popup will be opened so that the user can authenticate securely.
// Headless login with email
const user = await auth.login({
  directLoginOptions: {
    directLoginMethod: 'email',
    email: '[email protected]',
    marketingConsentStatus: 'opted_in'
  }
});

// Headless login with specific provider
const user = await auth.login({
  directLoginOptions: {
    directLoginMethod: 'google', // or 'apple', 'facebook'
    marketingConsentStatus: 'opted_in'
  }
});
OptionDescription
directLoginMethodThe authentication provider (email, google, apple, or facebook)
emailRequired when directLoginMethod is email, specifies the user’s email address
marketingConsentStatusMarketing consent setting (opted_in or unsubscribed)

Login with EthersJS

Integrates Passport authentication with EthersJS for wallet connection and interaction.
const provider = await connectWallet({ auth });
const web3Provider = new BrowserProvider(passportProvider);
const accounts = await web3Provider.send('eth_requestAccounts', []);
This implementation uses EthersJS’s BrowserProvider to interact with the Passport provider. It requests user accounts and manages the connection state, displaying the connected account address when successful.

Identity-only Login

The login method can be used to log in a user and retrieve their profile information without connecting to Immutable zkEVM.
const profile: User | null = await auth.login();
This will prompt the user to select an authentication option from within an iFrame, and open a popup to securely complete the authentication process.

Handle the Callback

On your redirect URI page, process the authentication callback:
// pages/callback.tsx or app/callback/page.tsx
import { useEffect } from 'react';
import { useRouter } from 'next/router';

export default function Callback() {
  const router = useRouter();

  useEffect(() => {
    async function handleCallback() {
      try {
        await auth.loginCallback();
        router.push('/dashboard');
      } catch (error) {
        console.error('Callback error:', error);
        router.push('/login?error=callback_failed');
      }
    }
    handleCallback();
  }, []);

  return (
    <div className="flex items-center justify-center min-h-screen">
      <p>Completing login...</p>
    </div>
  );
}

Get User Information

User Profile

// Get user info from the ID token
const userInfo = await auth.getUserInfo();

console.log({
  userId: userInfo.sub,          // Unique Passport user ID
  email: userInfo.email,         // If email scope granted
  emailVerified: userInfo.email_verified,
});

Session Management

Check If Logged In

async function isLoggedIn(): Promise<boolean> {
  return auth.isLoggedIn();
}

Get Access Token

For authenticated API calls to your backend:
const accessToken = await auth.getAccessToken();

// Use in API requests
const response = await fetch('/api/user/inventory', {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

Get ID Token

The ID token contains user identity claims:
const idToken = await auth.getIdToken();
// JWT containing user claims (sub, email, etc.)

Logout

Default behavior - logs out without redirecting.
async function logout() {
  try {
    await passportInstance.logout();
  } catch (error) {
    console.error('Logout failed:', error);
  }
}

Logout with Redirect

To redirect users after logout, configure logoutMode: 'redirect' at initialization, then call logout:
// 1. Configure redirect mode
const passportInstance = new passport.Passport({
  baseConfig: {
    environment: config.Environment.SANDBOX,
    publishableKey: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY,
  },
  clientId: process.env.NEXT_PUBLIC_CLIENT_ID,
  redirectUri: 'http://localhost:3000/redirect',
  logoutMode: 'redirect',
  logoutRedirectUri: 'http://localhost:3000/logout',
  audience: 'platform_api',
  scope: 'openid offline_access email transact',
});

// 2. Call logout - will redirect to logoutRedirectUri
async function logout() {
  try {
    await passportInstance.logout();
  } catch (error) {
    console.error('Logout failed:', error);
  }
}

Error Handling

Common Logout Issues:
ErrorCauseSolution
Logout callback timeoutSilent logout page didn’t loadVerify logoutRedirectUri is accessible
Wallet still connectedCleanup order wrongDisconnect wallet before passport.logout()
Session persists (Unreal)Soft logout usedSet DoHardLogout: true
Example (TypeScript):
try {
  await passportInstance.logout();
} catch (error) {
  console.error('Logout failed:', error);
  // Fallback: clear local state anyway
  clearUserState();
}

Backend JWT Validation

Validate Passport JWTs on your server to secure API endpoints.

Node.js with jose

import { createRemoteJWKSet, jwtVerify } from 'jose';

// Create JWKS client (cache this)
const JWKS = createRemoteJWKSet(
  new URL('https://auth.immutable.com/.well-known/jwks.json')
);

export async function validatePassportToken(token: string) {
  try {
    const { payload } = await jwtVerify(token, JWKS, {
      issuer: 'https://auth.immutable.com/',
      audience: 'platform_api',
    });
    
    return {
      valid: true,
      userId: payload.sub,
      email: payload.email,
    };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

Express Middleware

import { Request, Response, NextFunction } from 'express';

export async function requireAuth(
  req: Request, 
  res: Response, 
  next: NextFunction
) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }
  
  const token = authHeader.slice(7);
  const result = await validatePassportToken(token);
  
  if (!result.valid) {
    return res.status(401).json({ error: 'Invalid token' });
  }
  
  req.user = { id: result.userId, email: result.email };
  next();
}

// Usage
app.get('/api/inventory', requireAuth, (req, res) => {
  const userId = req.user.id;
  // Fetch inventory for this user
});

JWT Claims Reference

ClaimTypeDescription
substringUnique Passport user ID
issstringAlways https://auth.immutable.com/
audstringYour audience (e.g., platform_api)
expnumberExpiration timestamp
iatnumberIssued at timestamp
emailstringUser’s email (if scope granted)
email_verifiedbooleanWhether email is verified

Error Handling

Handle authentication errors gracefully to provide better user experience:
import { PassportError, PassportErrorType } from '@imtbl/auth';

async function handleLogin() {
  try {
    await auth.login();
    console.log('Login successful');
  } catch (error) {
    if (error instanceof PassportError) {
      switch (error.type) {
        case PassportErrorType.AUTHENTICATION_ERROR:
          console.error('Login failed:', error.message);
          // Show user-friendly error message
          break;
        case PassportErrorType.USER_REGISTRATION_ERROR:
          console.error('User registration failed:', error.message);
          // Handle registration failure
          break;
        case PassportErrorType.WALLET_CONNECTION_ERROR:
          console.error('Wallet connection failed:', error.message);
          // Retry connection or show error
          break;
        default:
          console.error('Unknown error:', error);
      }
    }
    throw error;
  }
}

Common Error Types

Error TypeDescriptionRecommended Action
AUTHENTICATION_ERRORLogin or authentication failedAsk user to try again or use different provider
USER_REGISTRATION_ERRORUser registration failedCheck if user already exists or retry
WALLET_CONNECTION_ERRORFailed to connect walletRetry connection or check network
INVALID_CONFIGURATIONInvalid Passport configurationVerify clientId and redirectUri

Next Steps