Skip to Content
GuideCore Integration
View .md

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/sso

The 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 providerAddress value.

Mobile users can skip the QR code — redirect them straight to the deep link with window.location.href = deep_link.

How Each Step Works

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 and SubtleCrypto (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:

StatusMeaning
pendingThe user hasn’t responded yet — keep polling
authorizedApproved — exchange response.authorization_code now
rejectedThe user denied the sign-in
expiredThe 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/token with grant_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/userinfo with Bearer token
  • Automatically refreshes token on 401 error
  • Returns user info or null if 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 sessionStorage

Storage

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

Last updated on