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.
- Go to the Developer Console in your UniAuth dashboard.
- Click Create Application.
- 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). - Copy your
client_idandclient_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/jsimport { 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/jsimport { 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.
| Scope | Claims Returned |
|---|---|
openid | sub |
profile | name, given_name, family_name, preferred_username, nickname, picture, profile, website, gender, birthdate, locale, zoneinfo, updated_at |
email | email, email_verified |
phone | phone_number, phone_number_verified |
address | address (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 revokedImportant: 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:
| Issue | Solution |
|---|---|
| redirect_uri mismatch | The 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 errors | The 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 reuse | Authorization 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 tokens | JWT 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" error | Users 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 rejected | UniAuth 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:
React
Pre-built hooks and components with the @uniauth/react SDK.
Next.js
Server-side token exchange with App Router and middleware.
PHP
Single-file SDK with zero dependencies for any PHP project.
Token Storage Recommendations
How you store tokens depends on your application architecture. Follow these best practices to minimize the risk of token theft:
| App Type | Recommended Storage | Notes |
|---|---|---|
| Server-side web app | HTTP-only, Secure, SameSite cookies | Store 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 application | Platform secure storage | Use iOS Keychain or Android Keystore. Never store tokens in SharedPreferences, UserDefaults, or plain files. |
| Desktop application | OS credential manager | Use 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.