Alien SSO Core Integration Guide: Vanilla JavaScript Setup
Integrate Alien SSO into any JavaScript or TypeScript project with the
core SDK — no framework required. The client targets modern browser
environments with localStorage and sessionStorage available.
Running outside the browser. The sign-in flow needs browser storage. Node/SSR code can still import the SDK safely (it falls back to in-memory token storage), but backend services should verify JWTs locally or use a standard OAuth library via the redirect flow instead.
Quick Start
Testing prerequisite. Completing a sign-in requires the Alien App on a phone with an Alien ID — that’s what scans the QR code and approves the request.
npm install @alien-id/ssoThe whole flow — create a client, show a QR code, poll for approval, exchange the code, read the user — fits in one block:
import { AlienSsoClient } from '@alien-id/sso';
// 1. Create the client
const client = new AlienSsoClient({
ssoBaseUrl: 'https://sso.alien-api.com',
providerAddress: 'your-provider-address',
});
// 2. Generate a deep link and show it as a QR code
const { deep_link, polling_code } = await client.generateDeeplink();
displayQRCode(deep_link); // your UI code
// 3. Poll until the user approves in the Alien App
const pollInterval = setInterval(async () => {
const response = await client.pollAuth(polling_code);
if (response.status === 'authorized') {
clearInterval(pollInterval);
// 4. Exchange the authorization code for tokens
const tokens = await client.exchangeToken(response.authorization_code);
console.log('Got tokens', tokens);
// 5. Verify and read the user
const userInfo = await client.verifyAuth();
console.log('Signed in as', userInfo?.sub);
} else if (response.status === 'rejected' || response.status === 'expired') {
clearInterval(pollInterval);
console.error('Sign-in failed:', response.status);
}
}, 5000);Provider address required. Register your app in the Developer Portal to get the
providerAddressvalue.
Mobile users can skip the QR code — redirect them straight to the
deep link with window.location.href = deep_link.
How Each Step Works
Generate the Deep Link
const { deep_link, polling_code, expired_at } = await client.generateDeeplink();The generateDeeplink() method:
- Sends
GET /oauth/authorize?response_type=code&response_mode=json&... - Generates PKCE code verifier/challenge (S256)
- Stores code verifier in
sessionStorage - Returns deep link for user authentication
Secure context required.
generateDeeplink()throws in environments without a CSPRNG andSubtleCrypto(crypto.getRandomValues/crypto.subtle) — e.g. non-HTTPS pages outside localhost or very old runtimes.
Poll for Authorization
pollAuth(polling_code) reports where the sign-in stands:
| Status | Meaning |
|---|---|
pending | The user hasn’t responded yet — keep polling |
authorized | Approved — exchange response.authorization_code now |
rejected | The user denied the sign-in |
expired | The sign-in session timed out — generate a new deep link |
Exchange the Code
const tokens = await client.exchangeToken(authorizationCode);
// Returns: { access_token, id_token, refresh_token, token_type, expires_in }The exchangeToken() method:
- Sends
POST /oauth/tokenwithgrant_type=authorization_code - Retrieves code verifier from
sessionStorage - Stores tokens via the configured token storage (browser default:
localStorage) - Returns the full token response
Verify the User
const userInfo = await client.verifyAuth();
if (userInfo) {
console.log('User ID:', userInfo.sub);
}The verifyAuth() method:
- Sends
GET /oauth/userinfowith Bearer token - Automatically refreshes token on 401 error
- Returns user info or
nullif not authenticated
Manage Tokens
Read stored tokens with the getters:
// Access token (for API calls)
const accessToken = client.getAccessToken();
// ID token (contains user claims)
const idToken = client.getIdToken();
// Refresh token (for getting new access tokens)
const refreshToken = client.getRefreshToken();Read the verified ID-token claims with getAuthData():
const claims = client.getAuthData();
if (claims) {
console.log('Subject (user ID):', claims.sub);
console.log('Issuer:', claims.iss);
console.log('Audience:', claims.aud);
console.log('Expires at:', new Date(claims.exp * 1000));
}Check expiry before making calls:
// Check if token is expired
if (client.isTokenExpired()) {
console.log('Token expired');
}
// Check if token will expire soon (within 5 minutes)
if (client.isAccessTokenExpired()) {
console.log('Token expiring soon, should refresh');
}client.getSubject() returns just the sub claim — the user ID.
Refresh Tokens
Access tokens expire. Use refresh tokens to get new access tokens without sending the user through sign-in again.
Manual Refresh
try {
const newTokens = await client.refreshAccessToken();
console.log('New access token:', newTokens.access_token);
} catch (error) {
// Refresh failed — the SDK has already cleared the stored tokens;
// send the user back through sign-in
}Automatic Refresh
withAutoRefresh wraps any authenticated request:
const result = await client.withAutoRefresh(async () => {
return await fetchProtectedResource();
});It executes your function; on a 401 failure it refreshes the token and retries once; if the refresh fails, it throws the original error.
async function fetchUserData() {
return await client.withAutoRefresh(async () => {
const response = await fetch('/api/user', {
headers: {
Authorization: `Bearer ${client.getAccessToken()}`
}
});
if (response.status === 401) {
const error = new Error('Unauthorized');
error.response = { status: 401 };
throw error;
}
return response.json();
});
}Log Out
client.logout();
// Clears all tokens from localStorage and sessionStorageStorage
Token storage is pluggable: the browser default keeps tokens in
localStorage and the transient PKCE/state/nonce values in
sessionStorage, and you can supply your own adapter via the
tokenStorage config option. See
Token Storage
for the adapters and the full
storage key list.
Handle Errors
try {
const { deep_link, polling_code } = await client.generateDeeplink();
} catch (error) {
// Network error, server error, or invalid provider
console.error('Failed to generate deep link:', error.message);
}
try {
const tokens = await client.exchangeToken(authorizationCode);
} catch (error) {
// Invalid code, expired code, or missing code_verifier
console.error('Token exchange failed:', error.message);
}
try {
await client.refreshAccessToken();
} catch (error) {
// Refresh token expired or revoked — stored tokens are already
// cleared; send the user back through sign-in
console.error('Refresh failed:', error.message);
}Complete Example
import { AlienSsoClient } from '@alien-id/sso';
const client = new AlienSsoClient({
ssoBaseUrl: 'https://sso.alien-api.com',
providerAddress: 'your-provider-address',
});
async function authenticate() {
try {
// Check if already authenticated
const userInfo = await client.verifyAuth();
if (userInfo) {
console.log('Already authenticated:', userInfo.sub);
return;
}
// Generate deep link
const { deep_link, polling_code } = await client.generateDeeplink();
// Display QR code (your UI code)
displayQRCode(deep_link);
// Poll for authorization
const pollInterval = setInterval(async () => {
const response = await client.pollAuth(polling_code);
if (response.status === 'authorized') {
clearInterval(pollInterval);
// Exchange code for tokens
const tokens = await client.exchangeToken(response.authorization_code);
console.log('Got tokens:', tokens);
// Get user info
const userInfo = await client.verifyAuth();
console.log('User:', userInfo?.sub);
hideQRCode();
} else if (response.status === 'rejected' || response.status === 'expired') {
clearInterval(pollInterval);
console.error('Authentication failed:', response.status);
hideQRCode();
}
}, 5000);
} catch (error) {
console.error('Authentication error:', error);
}
}Next Steps
- Core API Reference — every method and type
- React Integration — pre-built provider, hooks and components
- Verify tokens on your backend — validate JWTs server-side
- Demo App — run the example app