Alien SSO React Integration: AlienSsoProvider Setup Guide
Add “Sign in with Alien” to a React app with the React SDK’s provider, hook, and pre-built components — working sign-in in about five minutes.
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.
1. Install
npm install @alien-id/sso-reactThe React SDK requires React 19.1.1 or higher and automatically
includes @alien-id/sso as a dependency.
2. Wrap your app
import { AlienSsoProvider } from '@alien-id/sso-react';
function App() {
return (
<AlienSsoProvider
config={{
ssoBaseUrl: 'https://sso.alien-api.com',
providerAddress: 'your-provider-address',
}}
>
<YourApp />
</AlienSsoProvider>
);
}
export default App;Provider address required. Register your app in the Developer Portal to get the
providerAddressvalue.
3. Add the sign-in button
import { SignInButton } from '@alien-id/sso-react';
function LoginPage() {
return (
<div>
<h1>Welcome</h1>
<SignInButton />
</div>
);
}Run it — you should see the Sign in with Alien ID button. Clicking it opens the QR modal; scanning the code with the Alien App approves the sign-in.
4. Read the user
import { useAuth } from '@alien-id/sso-react';
function Dashboard() {
const { auth, logout } = useAuth();
if (!auth.isAuthenticated) {
return <div>Not signed in</div>;
}
return (
<div>
<p>User ID: {auth.tokenInfo?.sub}</p>
<button onClick={logout}>Log out</button>
</div>
);
}You now have working Alien sign-in. The provider renders the QR modal, polls for approval, exchanges the authorization code for tokens, and stores them. The rest of this page covers configuration, token refresh, and custom flows.
How Sign-In Works
The SDK drives the full OAuth flow for you: it generates a PKCE-backed deep link, shows it as a QR code, polls until the user approves in the Alien App, then exchanges the authorization code for tokens. Your code only reads the resulting auth state. See How Alien SSO works for the flow diagram and a step-by-step walkthrough.
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
ssoBaseUrl | string | — | Base URL of the SSO service (required) |
providerAddress | string | — | Your provider address from the Developer Portal (required) |
pollingInterval | number | 5000 | Polling interval in ms |
All other core client options — redirectUri, tokenStorage, dpop,
allowInsecureSsoBaseUrl — pass through the config object unchanged.
See the core API reference
when you need them.
Auth State and Methods
The useAuth() hook provides the auth state and every method you need
to drive a custom flow. The auth object contains:
{
isAuthenticated: boolean;
token: string | null; // access token
tokenInfo: TokenInfo | null; // verified ID-token claims
}tokenInfo holds the ID-token claims (sub, iss, aud, exp, …)
that were verified at token exchange — see
TokenInfo for the
exact shape.
const {
client, // Direct access to AlienSsoClient instance
auth, // Authentication state
queryClient, // React Query client instance
generateDeeplink, // Generate authentication deep link
pollAuth, // Poll for authentication status
exchangeToken, // Exchange authorization code for tokens
verifyAuth, // Verify current token (calls /oauth/userinfo)
refreshToken, // Refresh access token
logout, // Clear authentication state
openModal, // Open built-in sign-in modal
closeModal, // Close sign-in modal
isModalOpen // Modal open state
} = useAuth();
// Abbreviated — the context also exposes getAccessToken, pollingInterval,
// agentIdEnabled and agentIdSkillUrl. See the React API Reference.Control the Sign-In Modal
The provider automatically renders the sign-in modal and handles the complete authentication flow — QR code display, polling, and token exchange.
Do not render
<SignInModal />yourself. The provider already mounts it; manual instances render nothing (an internal slot ensures only the provider-owned instance mounts). The component takes no props — open and close it exclusively viauseAuth().openModal()/closeModal().
import { useAuth } from '@alien-id/sso-react';
function CustomButton() {
const { openModal } = useAuth();
return <button onClick={openModal}>Sign In with Alien</button>;
}Keep Tokens Fresh
Use refreshToken to refresh the access token before it expires:
import { useAuth } from '@alien-id/sso-react';
function MyComponent() {
const { refreshToken, auth, logout } = useAuth();
async function handleApiCall() {
// Check if token is expiring soon
if (auth.tokenInfo && auth.tokenInfo.exp * 1000 < Date.now() + 60000) {
const accessToken = await refreshToken();
if (!accessToken) {
// Refresh failed, redirect to login
return;
}
}
// Make API call with fresh token
const response = await fetch('/api/data', {
headers: {
Authorization: `Bearer ${auth.token}`
}
});
}
}Automatic Refresh with Axios
For automatic token refresh on API calls, use an axios interceptor:
import { useAuth } from '@alien-id/sso-react';
import axios from 'axios';
import { useMemo, useRef } from 'react';
export function useAxios() {
const { auth, logout, refreshToken } = useAuth();
const isRefreshing = useRef(false);
const failedQueue = useRef([]);
return useMemo(() => {
const instance = axios.create({
baseURL: '/api',
});
// Add token to requests
instance.interceptors.request.use((config) => {
if (auth.token) {
config.headers.Authorization = `Bearer ${auth.token}`;
}
return config;
});
// Handle 401 responses
instance.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status !== 401 || originalRequest._retry) {
return Promise.reject(error);
}
if (isRefreshing.current) {
// Queue request while refreshing
return new Promise((resolve, reject) => {
failedQueue.current.push({ resolve, reject });
}).then(() => instance(originalRequest));
}
originalRequest._retry = true;
isRefreshing.current = true;
try {
const accessToken = await refreshToken();
if (accessToken) {
failedQueue.current.forEach((p) => p.resolve());
failedQueue.current = [];
return instance(originalRequest);
}
throw new Error('Refresh failed');
} catch (refreshError) {
failedQueue.current.forEach((p) => p.reject(refreshError));
failedQueue.current = [];
logout();
return Promise.reject(refreshError);
} finally {
isRefreshing.current = false;
}
}
);
return instance;
}, [auth.token, logout, refreshToken]);
}Build a Custom Sign-In UI
If you want a custom UI instead of the built-in modal, drive the flow
yourself with generateDeeplink, pollAuth, and exchangeToken:
import { useAuth } from '@alien-id/sso-react';
import { useState, useEffect } from 'react';
import { QRCodeSVG } from 'qrcode.react';
function CustomAuth() {
const { generateDeeplink, pollAuth, exchangeToken, auth } = useAuth();
const [deepLink, setDeepLink] = useState<string | null>(null);
const [pollingCode, setPollingCode] = useState<string | null>(null);
const handleSignIn = async () => {
const response = await generateDeeplink();
setDeepLink(response.deep_link);
setPollingCode(response.polling_code);
};
useEffect(() => {
if (!pollingCode) return;
const interval = setInterval(async () => {
const response = await pollAuth(pollingCode);
if (response.status === 'authorized') {
clearInterval(interval);
await exchangeToken(response.authorization_code!);
setDeepLink(null);
setPollingCode(null);
} else if (response.status === 'rejected' || response.status === 'expired') {
clearInterval(interval);
setDeepLink(null);
setPollingCode(null);
}
}, 5000);
return () => clearInterval(interval);
}, [pollingCode, pollAuth, exchangeToken]);
if (auth.isAuthenticated) {
return <div>Signed in as {auth.tokenInfo?.sub}</div>;
}
if (deepLink) {
return (
<div>
<QRCodeSVG value={deepLink} size={256} />
<p>Scan with Alien App</p>
</div>
);
}
return <button onClick={handleSignIn}>Sign In with Alien</button>;
}Protect Routes
Gate routes on the auth state:
import { useAuth } from '@alien-id/sso-react';
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { auth } = useAuth();
if (!auth.isAuthenticated) {
return <Navigate to="/login" />;
}
return <>{children}</>;
}
// Usage
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />Complete Example
import { AlienSsoProvider, useAuth, SignInButton } from '@alien-id/sso-react';
import { useEffect } from 'react';
function App() {
return (
<AlienSsoProvider
config={{
ssoBaseUrl: 'https://sso.alien-api.com',
providerAddress: 'your-provider-address'
}}
>
<Dashboard />
</AlienSsoProvider>
);
}
function Dashboard() {
const { auth, logout, verifyAuth } = useAuth();
// Verify token on mount
useEffect(() => {
if (auth.token) {
verifyAuth();
}
}, []);
if (!auth.isAuthenticated) {
return (
<div>
<h1>Welcome to My App</h1>
<SignInButton />
</div>
);
}
return (
<div>
<h1>Dashboard</h1>
<p>User ID: {auth.tokenInfo?.sub}</p>
<p>Issuer: {auth.tokenInfo?.iss}</p>
<p>Expires: {new Date(auth.tokenInfo!.exp * 1000).toLocaleString()}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
export default App;TypeScript
The React SDK is fully typed. The client class and the shared flow
types all come from @alien-id/sso:
import type {
AlienSsoClient,
AuthorizeResponse,
PollResponse,
TokenResponse,
TokenInfo,
} from '@alien-id/sso';See the React API reference for the full list of exported types.
Next Steps
- React API Reference — hooks and components
- Core Integration — framework-free flow
- Verify tokens on your backend — validate JWTs server-side
- Demo App — run the example app