Auth Platform API
Self-hosted authentication microservice with OAuth 2.0 Authorization Code Flow (PKCE), email OTP verification, RS256 JWT tokens, and multi-tenant application management.
OAuth 2.0 + PKCE
Secure authorization code flow with no client secrets required on the frontend
Hosted Login
Beautiful login UI rendered by the auth platform, like Google or GitHub
RS256 JWT
Asymmetric key signing with automatic token refresh
Real-Time Revocation
SSE-powered instant session invalidation when admin force-logouts a user
Multi-Tenant
Register multiple apps with isolated users and settings
Quick Start
Get up and running in 3 steps:
Register Your Application
Open the Admin Dashboard and create a new application. You'll receive a Client ID.
Admin Dashboard: __AUTH_SERVER__/dashboard/
Download the SDK
Get our JavaScript SDK to integrate authentication into your app.
Download auth-sdk.jsInitialize & Use
Add the SDK to your HTML and initialize with your Client ID.
<script src="auth-sdk.js"></script>
<script>
const auth = new AuthClient({
AUTH_SERVER: '__AUTH_SERVER__',
CLIENT_ID: 'your-client-id',
REDIRECT_URI: window.location.origin
});
await auth.handleCallback();
if (auth.isAuthenticated()) {
console.log('Logged in as', auth.getUser().email);
auth.startAutoRefresh();
} else {
auth.login(); // Redirects to hosted login
}
</script>
Download SDK
Our JavaScript SDK handles all OAuth 2.0 + PKCE complexity for you. Just download, configure, and integrate.
JavaScript SDK
Drop-in authentication for any frontend framework
- OAuth 2.0 Authorization Code Flow with PKCE
- Automatic token refresh & SSE real-time revocation
- True encapsulation — internal state is inaccessible
- Auth change events with logout reason
- Zero dependencies & framework-agnostic
auth-sdk.js— Complete AuthClient class with true#private fields- PKCE crypto utilities (code verifier, SHA-256 challenge)
- Auto token refresh timer + SSE session stream for real-time revocation
- Works as
<script>tag (UMD) or ES moduleimport
Integration Guide
1. Basic Setup
Configure the SDK using environment variables — never hardcode secrets or URLs in source code.
// Read from your environment / .env file
const auth = new AuthClient({
AUTH_SERVER: '__AUTH_SERVER__', // or process.env.NEXT_PUBLIC_AUTH_SERVER
CLIENT_ID: 'app_xxxxxxxxxxxx', // from Admin Console
REDIRECT_URI: window.location.origin, // must match Admin Console
});
2. Handle Authentication
// On page load — check for OAuth callback
await auth.handleCallback();
if (auth.isAuthenticated()) {
const user = auth.getUser();
showDashboard(user);
auth.startAutoRefresh(); // token refresh + SSE revocation stream
} else {
showLoginButton();
}
3. Listen for Auth Changes
Get notified in real-time when the session state changes — including admin force-logouts.
auth.onAuthChange((isLoggedIn, reason) => {
if (!isLoggedIn) {
if (reason === 'revoked_by_admin') {
showBanner('Your session was ended by an administrator');
} else if (reason === 'session_expired') {
showBanner('Session expired — please log in again');
}
redirectToLogin();
}
});
4. Making Authenticated Requests
async function fetchUserData() {
const token = auth.getAccessToken();
const response = await fetch('https://your-api.com/user/profile', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
return response.json();
}
5. Logout
document.getElementById('logoutBtn').onclick = () => {
auth.logout();
window.location.href = '/';
};
Environment Setup
The SDK reads its configuration from a config object — use your framework's environment variables to supply the values. Never commit real credentials to source control.
Next.js
# .env.local
NEXT_PUBLIC_AUTH_SERVER=__AUTH_SERVER__
NEXT_PUBLIC_CLIENT_ID=app_xxxxxxxxxxxx
NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000/callback
// lib/config.js
export const AUTH_CONFIG = {
AUTH_SERVER: process.env.NEXT_PUBLIC_AUTH_SERVER,
CLIENT_ID: process.env.NEXT_PUBLIC_CLIENT_ID,
REDIRECT_URI: process.env.NEXT_PUBLIC_REDIRECT_URI,
};
Vite (Vue / React / Svelte)
# .env
VITE_AUTH_SERVER=__AUTH_SERVER__
VITE_CLIENT_ID=app_xxxxxxxxxxxx
VITE_REDIRECT_URI=http://localhost:5173/callback
const auth = new AuthClient({
AUTH_SERVER: import.meta.env.VITE_AUTH_SERVER,
CLIENT_ID: import.meta.env.VITE_CLIENT_ID,
REDIRECT_URI: import.meta.env.VITE_REDIRECT_URI,
});
Create React App
# .env
REACT_APP_AUTH_SERVER=__AUTH_SERVER__
REACT_APP_CLIENT_ID=app_xxxxxxxxxxxx
REACT_APP_REDIRECT_URI=http://localhost:3000/callback
const auth = new AuthClient({
AUTH_SERVER: process.env.REACT_APP_AUTH_SERVER,
CLIENT_ID: process.env.REACT_APP_CLIENT_ID,
REDIRECT_URI: process.env.REACT_APP_REDIRECT_URI,
});
Plain HTML / Vanilla JS
Since plain HTML has no build step, pass values directly or use <meta> tags:
<script src="auth-sdk.js"></script>
<script>
const auth = new AuthClient({
AUTH_SERVER: '__AUTH_SERVER__',
CLIENT_ID: 'app_xxxxxxxxxxxx',
REDIRECT_URI: window.location.origin,
});
</script>
SDK Reference
The SDK uses true private fields (#) — internal state like tokens and streams are completely inaccessible from outside the class.
new AuthClient(config)
Create an auth client. Throws if AUTH_SERVER is missing.
const auth = new AuthClient({
AUTH_SERVER: process.env.NEXT_PUBLIC_AUTH_SERVER,
CLIENT_ID: process.env.NEXT_PUBLIC_CLIENT_ID,
REDIRECT_URI: process.env.NEXT_PUBLIC_REDIRECT_URI,
});
AUTH_SERVER | string | Required. Auth platform URL (from env). |
CLIENT_ID | string | OAuth Client ID from Admin Console. |
REDIRECT_URI | string | Callback URL — must match Admin Console. |
auth.login()
Redirect to the hosted login page (OAuth 2.0 + PKCE). Like "Sign in with Google" — your app never sees the password.
auth.login();
auth.logout(reason?)
Clear session, stop refresh timers & SSE stream, and fire the onAuthChange callback.
auth.logout(); // voluntary logout (reason = null)
auth.isAuthenticated() → boolean
Check if user has a valid, non-expired access token.
if (auth.isAuthenticated()) {
console.log('User is logged in');
}
auth.getUser() → object | null
Get current user info decoded from the JWT. Returns a frozen object (cannot be mutated).
const user = auth.getUser();
// { email, user_id, app_id, issuer, expires_at, issued_at }
auth.getAccessToken() → string | null
Get the raw JWT for Authorization headers.
fetch('/api/data', {
headers: { 'Authorization': `Bearer ${auth.getAccessToken()}` }
});
auth.handleCallback() → Promise<boolean>
Process the OAuth redirect. Call once on page load (or on your callback route). Returns true if tokens were received.
const handled = await auth.handleCallback();
auth.onAuthChange(callback)
Register a listener for authentication state changes. Fires on login, logout, and real-time admin revocation.
auth.onAuthChange((isLoggedIn, reason) => {
// reason: 'revoked_by_admin' | 'session_expired' | null
if (!isLoggedIn && reason === 'revoked_by_admin') {
alert('Admin ended your session');
}
});
auth.startAutoRefresh()
Enable two automatic features:
- Token refresh — refreshes access token before it expires (checks every 15 s)
- SSE revocation stream — opens a server-sent event connection that fires instantly when an admin force-logouts the user
if (auth.isAuthenticated()) {
auth.startAutoRefresh();
}
auth.refreshAccessToken() → Promise<boolean>
Manually refresh the access token. Normally handled automatically by startAutoRefresh().
const ok = await auth.refreshAccessToken();
auth.verifyToken() → Promise<object | null>
Verify the current access token server-side and get the decoded payload.
const payload = await auth.verifyToken();
auth.getTimeUntilExpiry() → number
Seconds remaining until the access token expires (0 if expired or missing).
console.log(`Token expires in ${auth.getTimeUntilExpiry()}s`);
OAuth Endpoints
/oauth/authorize
Start OAuth authorization flow. Renders hosted login page.
Query Parameters
client_id |
string | Your application's client ID |
redirect_uri |
string | Redirect URI after authentication |
response_type |
string | Must be "code" |
state |
string | CSRF protection token |
code_challenge |
string | PKCE code challenge (SHA256) |
code_challenge_method |
string | Must be "S256" |
/oauth/token
Exchange authorization code for access and refresh tokens.
Request Body
{
"grant_type": "authorization_code",
"code": "auth_code_here",
"client_id": "your_client_id",
"redirect_uri": "https://your-app.com/callback",
"code_verifier": "pkce_verifier"
}
Response
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "refresh_token_here"
}
Auth Endpoints
/auth/request-otp
Request OTP for email-based authentication.
Request Body
{
"email": "user@example.com",
"app_id": "app_xxxxxxxxxxxx"
}
/auth/verify-otp
Verify OTP and receive tokens.
Request Body
{
"email": "user@example.com",
"otp": "123456",
"app_id": "app_xxxxxxxxxxxx"
}
Response
{
"access_token": "eyJhbGc...",
"refresh_token": "refresh_token_here"
}
Token Management
/token/refresh
Refresh an access token using a refresh token.
Request Body
{
"refresh_token": "your_refresh_token"
}
Response
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 900
}
/token/verify
Verify token validity and decode payload.
Request Body
{
"token": "eyJhbGc..."
}
Admin Endpoints
Admin endpoints require authentication with admin credentials.
/admin/apps
List all registered applications.
/admin/apps
Create a new application.
Request Body
{
"app_name": "My App",
"redirect_uris": ["https://myapp.com/callback"],
"session_duration": 900,
"refresh_token_duration": 604800
}
/admin/users
List all users across apps.
/admin/stats
Get dashboard statistics (app count, user count, active sessions).
Code Examples
React / Next.js
// .env.local
// NEXT_PUBLIC_AUTH_SERVER=__AUTH_SERVER__
// NEXT_PUBLIC_CLIENT_ID=app_xxxxxxxxxxxx
// NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000/callback
import AuthClient from './auth-sdk';
import { AUTH_CONFIG } from './config';
import { useEffect, useState } from 'react';
function App() {
const [user, setUser] = useState(null);
const [logoutReason, setLogoutReason] = useState(null);
const [auth] = useState(() => new AuthClient(AUTH_CONFIG));
useEffect(() => {
async function init() {
await auth.handleCallback();
auth.onAuthChange((loggedIn, reason) => {
if (loggedIn) {
setUser(auth.getUser());
setLogoutReason(null);
} else {
setUser(null);
setLogoutReason(reason);
}
});
if (auth.isAuthenticated()) {
setUser(auth.getUser());
auth.startAutoRefresh();
}
}
init();
}, []);
if (logoutReason === 'revoked_by_admin') {
return <div className="banner">Admin ended your session</div>;
}
if (!user) {
return <button onClick={() => auth.login()}>Login</button>;
}
return (
<div>
<h1>Welcome, {user.email}</h1>
<button onClick={() => auth.logout()}>Logout</button>
</div>
);
}
Vue.js (Vite)
// .env
// VITE_AUTH_SERVER=__AUTH_SERVER__
// VITE_CLIENT_ID=app_xxxxxxxxxxxx
// VITE_REDIRECT_URI=http://localhost:5173/callback
import AuthClient from './auth-sdk';
export default {
data() {
return {
auth: new AuthClient({
AUTH_SERVER: import.meta.env.VITE_AUTH_SERVER,
CLIENT_ID: import.meta.env.VITE_CLIENT_ID,
REDIRECT_URI: import.meta.env.VITE_REDIRECT_URI,
}),
user: null,
logoutReason: null
};
},
async mounted() {
this.auth.onAuthChange((loggedIn, reason) => {
this.user = loggedIn ? this.auth.getUser() : null;
this.logoutReason = reason;
});
await this.auth.handleCallback();
if (this.auth.isAuthenticated()) {
this.user = this.auth.getUser();
this.auth.startAutoRefresh();
}
},
methods: {
login() { this.auth.login(); },
logout() { this.auth.logout(); }
}
};
Protected API Route (Express)
const jwt = require('jsonwebtoken');
const fs = require('fs');
const publicKey = fs.readFileSync('public_key.pem');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, publicKey, { algorithms: ['RS256'] }, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
app.get('/api/protected', authenticateToken, (req, res) => {
res.json({ message: 'Protected data', user: req.user });
});
Architecture & Flow Diagrams
Interactive sequence diagrams for all authentication flows — OAuth 2.0, OTP, WebAuthn, token refresh, multi-tenant isolation, and more.