Quick Start Guide

Get up and running with UniAuth in under 5 minutes. This guide walks you through registering your application, adding login to your frontend, handling the OAuth callback, and fetching user profile data.

Prerequisites: You need a UniAuth account and a registered application. If you don't have an account yet, sign up here.

Step 1: Register Your Application

Every application that authenticates users through UniAuth needs a registered OAuth client. This gives you a client_id and client_secret that identify your app during the login flow.

  1. Go to the Developer Console in your UniAuth dashboard.
  2. Click Create Application.
  3. Enter your application name and add at least one Redirect URI — this is the URL UniAuth will send users back to after they log in (e.g., https://yourapp.com/callback).
  4. Copy your client_id and client_secret. Store the secret securely — you won't be able to view it again.

Tip: For local development, use http://localhost:3000/callback as your redirect URI. You can add multiple URIs per application.

Step 2: Add Login to Your App

Choose the integration method that fits your stack. UniAuth provides official SDKs for JavaScript and React, or you can call the OAuth endpoints directly.

Option A: JavaScript SDK

Install the SDK and initialize it with your application credentials. The SDK handles PKCE challenge generation, state management, and token storage automatically.

npm install @uniauth/js
import { UniAuthClient } from '@uniauth/js';

const auth = new UniAuthClient({
  domain: 'https://uniauth.id',
  clientId: 'your-client-id',
  redirectUri: 'https://yourapp.com/callback',
  scope: 'openid profile email',
});

// Trigger login — redirects to UniAuth
document.getElementById('login-btn').addEventListener('click', () => {
  auth.login();
});

Option B: React SDK

Wrap your app with UniAuthProvider and use the useUniAuth() hook in any component.

npm install @uniauth/react @uniauth/js
import { UniAuthProvider, useUniAuth } from '@uniauth/react';

// 1. Wrap your app at the root
function App() {
  return (
    <UniAuthProvider
      domain="https://uniauth.id"
      clientId="your-client-id"
      redirectUri={window.location.origin + '/callback'}
      scope="openid profile email"
    >
      <YourApp />
    </UniAuthProvider>
  );
}

// 2. Use the hook in any component
function LoginButton() {
  const { isAuthenticated, isLoading, user, login, logout } = useUniAuth();

  if (isLoading) return <div>Loading...</div>;

  if (isAuthenticated) {
    return (
      <div>
        <span>Welcome, {user?.name}</span>
        <button onClick={() => logout()}>Log out</button>
      </div>
    );
  }

  return <button onClick={() => login()}>Sign in with UniAuth</button>;
}

Option C: Direct API (Any Language)

If you're not using JavaScript, construct the authorization URL manually. UniAuth requires PKCE (Proof Key for Code Exchange) on all authorization requests.

// 1. Generate a PKCE code verifier (43-128 char random string)
const codeVerifier = generateRandomString(64);

// 2. Create the code challenge (SHA-256 hash, base64url-encoded)
const encoder = new TextEncoder();
const digest = await crypto.subtle.digest('SHA-256', encoder.encode(codeVerifier));
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
  .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

// 3. Generate a random state parameter for CSRF protection
const state = generateRandomString(32);

// 4. Store codeVerifier and state (e.g., in sessionStorage)
sessionStorage.setItem('pkce_verifier', codeVerifier);
sessionStorage.setItem('oauth_state', state);

// 5. Redirect the user to UniAuth
const authUrl = new URL('https://uniauth.id/api/oauth/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'your-client-id');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', state);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');

window.location.href = authUrl.toString();

Step 3: Handle the Callback

After the user authenticates, UniAuth redirects them back to your redirect_uri with an authorization code. Exchange this code for access and ID tokens.

Using the SDK

Both the JavaScript and React SDKs handle the token exchange automatically:

// JavaScript SDK — on your callback page
const result = await auth.handleCallback();

if (result.error) {
  console.error('Login failed:', result.error);
} else {
  console.log('User:', result.user);       // { sub, email, name, ... }
  console.log('Token:', result.accessToken); // JWT access token
  window.location.href = '/dashboard';
}

Using the API Directly

Send a POST request to the token endpoint with the authorization code and your PKCE code verifier:

// On your callback page, extract the code and state from the URL
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const returnedState = params.get('state');

// Verify the state matches what you stored
const savedState = sessionStorage.getItem('oauth_state');
if (returnedState !== savedState) {
  throw new Error('State mismatch — possible CSRF attack');
}

// Exchange the code for tokens
const response = await fetch('https://uniauth.id/api/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: code,
    redirect_uri: 'https://yourapp.com/callback',
    client_id: 'your-client-id',
    client_secret: 'your-client-secret',
    code_verifier: sessionStorage.getItem('pkce_verifier'),
  }),
});

const tokens = await response.json();
// {
//   access_token: "eyJhbGciOiJIUzI1NiIs...",
//   token_type: "Bearer",
//   expires_in: 3600,
//   id_token: "eyJhbGciOiJSUzI1NiIs...",
//   refresh_token: "a1b2c3d4e5..."
// }

Security note: If your app has a backend, always exchange the code server-side so your client_secret is never exposed to the browser.

Step 4: Get User Info

Use the access token to fetch the authenticated user's profile from the UserInfo endpoint. The claims returned depend on the scopes you requested.

const userInfo = await fetch('https://uniauth.id/api/oauth/userinfo', {
  headers: {
    Authorization: `Bearer ${tokens.access_token}`,
  },
});

const user = await userInfo.json();
// With scope "openid profile email":
// {
//   sub: "2b7f16373f76cbdf9c0241ae59903ee2...",
//   name: "Jane Doe",
//   given_name: "Jane",
//   family_name: "Doe",
//   picture: "https://uniauth.id/api/avatar/...",
//   email: "[email protected]",
//   email_verified: true
// }

The sub claim is a pairwise identifier — it is unique to your application and stable across sessions. Use it as the primary key to link UniAuth users to your local database.

ScopeClaims Returned
openidsub
profilename, given_name, family_name, preferred_username, nickname, picture, profile, website, gender, birthdate, locale, zoneinfo, updated_at
emailemail, email_verified
phonephone_number, phone_number_verified
addressaddress (structured: street_address, locality, region, postal_code, country)

Step 5: Add Logout

To log users out, redirect them to the UniAuth end-session endpoint. Pass the id_token_hint so UniAuth can end the correct session, and a post_logout_redirect_uri to send them back to your app afterward.

// Using the SDK
await auth.logout({
  postLogoutRedirectUri: 'https://yourapp.com',
});

// Or manually construct the URL
const logoutUrl = new URL('https://uniauth.id/api/oauth/end-session');
logoutUrl.searchParams.set('id_token_hint', idToken);
logoutUrl.searchParams.set('post_logout_redirect_uri', 'https://yourapp.com');
window.location.href = logoutUrl.toString();

Refreshing Tokens

Access tokens expire after 1 hour. Use the refresh token to obtain new tokens without requiring the user to log in again:

const response = await fetch('https://uniauth.id/api/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: 'your-refresh-token',
    client_id: 'your-client-id',
    client_secret: 'your-client-secret',
  }),
});

const newTokens = await response.json();
// Save the new access_token and refresh_token
// The old refresh token is automatically revoked

Important: UniAuth uses refresh token rotation. Each time you use a refresh token, you receive a new one and the old one is invalidated. If a revoked refresh token is ever used again, all tokens in that family are immediately revoked to protect against token theft.

Next Steps

You now have a working UniAuth integration. Here are some recommended next steps:

  • Authentication — Learn about all supported authentication methods including passkeys and social login.
  • Multi-Factor Authentication — Add an extra layer of security with TOTP, email OTP, or SMS verification.
  • SDKs — Detailed documentation for the JavaScript, React, and PHP SDKs.
  • API Reference — Complete reference for all REST API endpoints.
  • OAuth2 / OIDC — Advanced OAuth configuration, scopes, and token management.
  • Account Setup — Configure your UniAuth profile, security settings, and developer console.

Error Handling

Token exchange and API calls can fail for various reasons. Always wrap authentication operations in error handling to provide a good user experience.

// Robust token exchange with error handling
async function exchangeCodeForTokens(code, codeVerifier) {
  try {
    const response = await fetch('https://uniauth.id/api/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: code,
        redirect_uri: 'https://yourapp.com/callback',
        client_id: 'your-client-id',
        client_secret: 'your-client-secret',
        code_verifier: codeVerifier,
      }),
    });

    const data = await response.json();

    if (!response.ok) {
      // Handle OAuth error response
      switch (data.error) {
        case 'invalid_grant':
          // Code expired or already used — restart the login flow
          console.error('Authorization code is invalid or expired');
          window.location.href = '/login';
          return null;
        case 'invalid_client':
          console.error('Client credentials are incorrect');
          return null;
        default:
          console.error('Token exchange failed:', data.error_description || data.error);
          return null;
      }
    }

    return data; // { access_token, id_token, refresh_token, ... }
  } catch (err) {
    // Network error — UniAuth may be unreachable
    console.error('Network error during token exchange:', err.message);
    return null;
  }
}

Troubleshooting

Common issues encountered during integration and how to resolve them:

IssueSolution
redirect_uri mismatchThe redirect URI in your authorization request must exactly match one registered in the Developer Console, including protocol, domain, port, path, and trailing slash. https://app.com/callback and https://app.com/callback/ are treated as different URIs.
CORS errorsThe token endpoint does not support browser-side requests from arbitrary origins. Exchange the authorization code on your backend server, not from client-side JavaScript. If you must use a SPA, use the JavaScript SDK which handles this correctly.
invalid_grant after code reuseAuthorization codes are single-use. If your callback page renders twice (common in React Strict Mode), the second exchange attempt will fail. Guard against duplicate exchanges by checking if the code has already been processed.
Clock skew affecting tokensJWT validation may fail if your server's clock is significantly out of sync. Ensure your server uses NTP for time synchronization. Allow a 30-second clock skew tolerance when validating token expiration.
"Email not verified" errorUsers must verify their email address before they can authorize third-party applications. Direct the user to check their inbox for the verification email, or call the resend verification endpoint.
Refresh token rejectedUniAuth uses refresh token rotation. Each refresh token can only be used once. If a rotated refresh token is reused, all tokens in that family are revoked as a security measure. Store the new refresh token from each refresh response.

Framework Guides

Detailed integration guides for popular frameworks with pre-built components and helpers:

Token Storage Recommendations

How you store tokens depends on your application architecture. Follow these best practices to minimize the risk of token theft:

App TypeRecommended StorageNotes
Server-side web appHTTP-only, Secure, SameSite cookiesStore tokens in encrypted, HTTP-only cookies so they are never accessible to JavaScript. This is the most secure approach.
Single-page application (SPA)In-memory (JavaScript variable)Store tokens in memory only. Never use localStorage or sessionStorage for access tokens — they are vulnerable to XSS attacks. Use the SDK's built-in memory storage.
Mobile applicationPlatform secure storageUse iOS Keychain or Android Keystore. Never store tokens in SharedPreferences, UserDefaults, or plain files.
Desktop applicationOS credential managerUse the operating system's credential storage (e.g., macOS Keychain, Windows Credential Manager, Linux Secret Service).

Security note: Refresh tokens are long-lived and grant the ability to obtain new access tokens. Treat them with the same care as passwords. If you suspect a refresh token has been compromised, revoke it immediately via the /api/oauth/revoke endpoint.