The Otim authentication system uses SIWE (EIP-4361) for secure authentication. Users sign a message with their wallet to prove ownership of their address, and receive an authorization token for subsequent Otim Client functions.

Authentication Flow

  1. Message Creation - Generate a SIWE message with domain, URI, and chain information
  2. Signature - User signs the message with their wallet
  3. Verification - Server verifies the signature and issues an authorization token
  4. Token Storage - Client stores the token for authenticated requests

Login Method

The login method handles the complete authentication flow, from message creation to token retrieval.

Quick Reference

const loginResponse = await otimClient.auth.login({
  domain: "otim.com",
  uri: "<https://app.otim.com>",
  address: "0x1234...",
  chainId: 1,
});

const jwtToken = loginResponse.authorization; // your jwt token

Method Signature

otimClient.auth.login(options: LoginOptions): Promise<AuthLoginResponse>

Parameters

interface LoginOptions {
  domain: string; // the domain requesting authentication (e.g., "otim.com")
  uri: string; // the uri of your application (e.g., "<https://app.otim.com>")
  address: Address; // wallet address
  chainId: number; // the wallet client chain id (use 0 for chain-agnostic auth)
}

Response

interface AuthLoginResponse {
  authorization: string; // jwt token for authenticated requests
}

Usage Examples

Frontend with Wagmi
import { useAccount, useWalletClient } from "wagmi";
import { createOtimClient } from "@otim/sdk";

function useLogin() {
  const { address, chain } = useAccount();
  const { data: walletClient } = useWalletClient();

  const login = async () => {
    const otimClient = createOtimClient({ walletClient });

    const loginResponse = await otimClient.auth.login({
      domain: window.location.host,
      uri: window.location.origin,
      address,
      chainId: chain.id,
    });

    await otimClient.auth.setAuthorizationHeader(loginResponse.authorization);
    
    /* 
     * after getting JWT, you can store it in localStorage or cookies
     *
     * localStorage.setItem('otimToken', loginResponse.authorization);
     * cookies.set('otimToken', loginResponse.authorization);
     *
     * for subsequent app loads, you can pass the stored token to otimClient:
     *
     * const otimClient = createOtimClient({
     *   walletClient,
     *   authorizationToken: localStorage.getItem('otimToken')
     * });
     *
     */

    return loginResponse.authorization; // returns JWT token
  };

  return { login };
}

Simple Backend Example

import { createOtimClient } from "@otim/sdk";
import { http } from "viem";
import { mainnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const privateKey = process.env.PRIVATE_KEY as `0x${string}`;
const account = privateKeyToAccount(privateKey);

const otimClient = createOtimClient({
  account,
  chain: mainnet,
  transport: http(),
});

const loginResponse = await otimClient.auth.login({
  domain: "api.otim.com",
  uri: "<https://api.otim.com>",
  address: account.address,
  chainId: 0,
});

await otimClient.auth.setAuthorizationHeader(loginResponse.authorization);

Considerations

  1. Domain Verification - Always use the correct domain to prevent phishing
  2. Message Content - The SIWE message includes Otim’s terms and conditions
  3. Nonce Generation - The SDK automatically generates a unique nonce

SIWE Message Details

The SDK automatically creates a SIWE message with the following content:
Welcome to Otim! By signing in, you accept the Otim Terms and Conditions (<https://otim.com/tac>).
This request will not trigger a blockchain transaction or cost any gas fees.

URI: [your-uri]
Version: 1
Chain ID: [chain-id]
Nonce: [timestamp-based-nonce]
Issued At: [current-timestamp]

Setting Authorization

The setAuthorizationHeader method was created for scenarios where you already have a JWT token and need to set it on the Otim client for subsequent authenticated API calls, but don’t have the context to provide it during client creation. The setAuthorizationHeader method sets a JWT token on an existing Otim Client instance.

Quick Reference

// you already have an otim jwt token:
const jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

// set it on the client for authenticated requests
await otimClient.auth.setAuthorizationHeader(jwtToken);

// now all subsequent requests will be authenticated in the same function context
const delegations = await otimClient.delegation.getUserDelegations();

Method Signature

otimClient.auth.setAuthorizationHeader(authorizationToken: string): Promise<void>

When to Use

This method is useful when:
  • You’re in a backend service that manages tokens separately
  • You need to switch between different authentication contexts
  • You’re working with tokens obtained through external authentication flows

When You Might NOT Need It (Frontend Apps)

For frontend applications, you often don’t need setAuthorizationHeader because you can provide the token directly during client creation:
// instead of creating client then setting token:
const otimClient = createOtimClient({ walletClient });
await otimClient.auth.setAuthorizationHeader(storedToken);

// you can provide the token directly:
const otimClient = createOtimClient({
  walletClient,
  authorizationToken: localStorage.getItem("otimToken"),
});
This approach is cleaner for frontend apps where tokens are stored in localStorage/cookies and loaded during app initialization.

Usage Examples

Setting a JWT token on an existing client:
// you have an otim jwt token from somewhere (login, storage, etc.)
const jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

// set it on the client
await otimClient.auth.setAuthorizationHeader(jwtToken);

// now make authenticated requests
const config = await otimClient.config.getConfig();
For backend services that manage tokens separately:
import { createOtimClient } from "@otim/sdk";

// create client without authentication
const otimClient = createOtimClient({
  account,
  chain: mainnet,
  transport: http(),
});

// later, when you have a token (from cache, database, etc.)
const storedToken = await getTokenFromDatabase(userId);
await otimClient.auth.setAuthorizationHeader(storedToken);

// now client is authenticated
const delegations = await otimClient.delegation.getUserDelegations();
Frontend – Complete Login Flow with Token Storage:
import { useMemo } from "react";
import { useAccount, useWalletClient } from "wagmi";
import { createOtimClient } from "@otim/sdk";
import { useCookies } from "@utils/cookies";

export function useOtimAuth() {
  const { address, chain } = useAccount();
  const { data: walletClient } = useWalletClient();
  
  // store auth token in cookies (or localStorage)
  const [authToken, setAuthToken] = useCookies<string | undefined>("otimToken");
  
  // create client with stored token
  const otimClient = useMemo(() => {
    if (!walletClient) return null;

    return createOtimClient({
      walletClient,
      authorizationToken: authToken || undefined,
    });
  }, [walletClient, authToken]);
  
  // login function
	const login = async () => {
    if (!otimClient || !address || !chain) {
      throw new Error("Wallet not connected");
    }
    
    // perform login
    const result = await otimClient.auth.login({
      domain: window.location.host,
      uri: window.location.origin,
      address,
      chainId: chain.id,
    });
    
    // store the token
		setAuthToken(result.authorization);

    return result.authorization;
  };

  const logout = () => {
    setAuthToken(undefined);
  };

  return {
    isAuthenticated: !!authToken,
    login,
    logout,
  };
}
This approach is cleaner because:
  • Token is provided during client creation, not set afterward
  • Client automatically recreates when token changes
  • No need for setAuthorizationHeader calls
  • Reactive to token state changes

Logout Method

The logout method handles the complete logout process: it calls the Otim API to invalidate the session server-side and clears the local authentication state from the client. This ensures proper cleanup of both client and server authentication state.

Quick Reference

// logout user and cleanup authentication state
await otimClient.auth.logout();

// after logout, the client is no longer authenticated
// any subsequent api calls will fail until re-authentication

Method Signature

otimClient.auth.logout(): Promise<AuthLogoutResponse>

Usage Examples

Simple logout call:
// logout user and clean up authentication
await otimClient.auth.logout();

// client is now unauthenticated
console.log("User logged out successfully");

Why Proper Logout Matters

Logging out is important because:
  • Calls the API to invalidate server-side session
  • Client automatically becomes really unauthenticated after logout