Skip to Content
GuideOAuth2 Clients
View .md

Alien SSO OAuth2 Client Configuration Guide

Alien SSO is a fully OIDC-compliant identity provider, so any standard OAuth 2.0 / OpenID Connect library can integrate with it. All you need is a provider address from the Developer Portal — it doubles as your OAuth client_id.

Key Configuration Parameters

SettingValue
Issuer URLhttps://sso.alien-api.com
Client IDYour provider address
Client SecretEmpty string (public client)
Token Auth Methodnone
PKCERequired (S256 only — plain is rejected)
Response Typecode

PKCE note. The code_verifier must be 43–128 unreserved characters per RFC 7636. The server currently tolerates verifiers up to 256 characters as a transitional allowance for legacy clients — do not rely on it.

Register the full callback URL. For redirect-based libraries — everything on this page — the server validates redirect_uri by exact match against your registered Allowed Origins entries (plus the RFC 8252 loopback port exception). Register the complete callback URL, e.g. https://your-app.com/api/auth/callback/alien, in the Developer Portal — registering just the origin fails with invalid_request: redirect_uri is not allowed for this client. Fragments in redirect_uri are rejected.

NextAuth.js (Auth.js)

NextAuth.js (now Auth.js) is the most popular authentication library for Next.js applications.

Basic Setup

// src/auth.ts import NextAuth from "next-auth" export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [{ id: "alien", name: "Alien", type: "oidc", issuer: "https://sso.alien-api.com", wellKnown: "https://sso.alien-api.com/.well-known/openid-configuration", clientId: process.env.ALIEN_PROVIDER_ADDRESS!, clientSecret: "", // Public client - no secret needed client: { token_endpoint_auth_method: "none", }, checks: ["pkce", "state"], authorization: { params: { scope: "openid", }, }, }], })
// src/app/api/auth/[...nextauth]/route.ts import { handlers } from "@/auth" export const { GET, POST } = handlers

With Refresh Tokens

For long-lived sessions with automatic token refresh:

// src/auth.ts import NextAuth from "next-auth" import type { JWT } from "next-auth/jwt" // Token refresh function async function refreshAccessToken(token: JWT): Promise<JWT> { try { const response = await fetch("https://sso.alien-api.com/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: token.refreshToken as string, client_id: process.env.ALIEN_PROVIDER_ADDRESS!, }), }) const refreshedTokens = await response.json() if (!response.ok) { throw refreshedTokens } return { ...token, accessToken: refreshedTokens.access_token, accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000, refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, idToken: refreshedTokens.id_token ?? token.idToken, } } catch (error) { console.error("Error refreshing access token:", error) return { ...token, error: "RefreshAccessTokenError", } } } export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [{ id: "alien", name: "Alien", type: "oidc", issuer: "https://sso.alien-api.com", wellKnown: "https://sso.alien-api.com/.well-known/openid-configuration", clientId: process.env.ALIEN_PROVIDER_ADDRESS!, clientSecret: "", client: { token_endpoint_auth_method: "none", }, checks: ["pkce", "state"], authorization: { params: { scope: "openid", }, }, }], callbacks: { async jwt({ token, account }) { // Initial sign in if (account) { return { ...token, accessToken: account.access_token, accessTokenExpires: account.expires_at! * 1000, refreshToken: account.refresh_token, idToken: account.id_token, } } // Return previous token if the access token has not expired yet if (Date.now() < (token.accessTokenExpires as number)) { return token } // Access token has expired, try to refresh it return await refreshAccessToken(token) }, async session({ session, token }) { session.accessToken = token.accessToken as string session.error = token.error as string | undefined return session }, }, })

Extend Session Types

// src/types/next-auth.d.ts import "next-auth" declare module "next-auth" { interface Session { accessToken?: string error?: string } } declare module "next-auth/jwt" { interface JWT { accessToken?: string accessTokenExpires?: number refreshToken?: string idToken?: string error?: string } }

Handle Token Refresh Errors

// src/components/SessionProvider.tsx "use client" import { useSession, signIn } from "next-auth/react" import { useEffect } from "react" export function SessionRefreshHandler({ children }: { children: React.ReactNode }) { const { data: session } = useSession() useEffect(() => { if (session?.error === "RefreshAccessTokenError") { // Force sign in to resolve the error signIn("alien") } }, [session?.error]) return <>{children}</> }

Usage in Components

// src/app/page.tsx import { auth, signIn, signOut } from "@/auth" export default async function Home() { const session = await auth() if (!session) { return ( <form action={async () => { "use server" await signIn("alien") }}> <button type="submit">Sign in with Alien</button> </form> ) } return ( <div> <p>User ID: {session.user?.id}</p> <form action={async () => { "use server" await signOut() }}> <button type="submit">Sign out</button> </form> </div> ) }

API Route with Token

// src/app/api/protected/route.ts import { auth } from "@/auth" import { NextResponse } from "next/server" export async function GET() { const session = await auth() if (!session?.accessToken) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) } // Use accessToken for downstream API calls const response = await fetch("https://your-api.com/data", { headers: { Authorization: `Bearer ${session.accessToken}`, }, }) return NextResponse.json(await response.json()) }

Generic OAuth 2.0 Client

For any language or framework, implement the OAuth 2.0 flow manually in seven steps:

1. Discovery Document

First, fetch the OIDC configuration:

const discovery = await fetch( "https://sso.alien-api.com/.well-known/openid-configuration" ) const config = await discovery.json() // config contains: // { // authorization_endpoint: "https://sso.alien-api.com/oauth/authorize", // token_endpoint: "https://sso.alien-api.com/oauth/token", // userinfo_endpoint: "https://sso.alien-api.com/oauth/userinfo", // jwks_uri: "https://sso.alien-api.com/oauth/jwks", // ... // }

2. Generate PKCE Challenge

import crypto from "crypto" function generateCodeVerifier(): string { return crypto.randomBytes(32).toString("base64url") } function generateCodeChallenge(verifier: string): string { return crypto.createHash("sha256").update(verifier).digest("base64url") } const codeVerifier = generateCodeVerifier() const codeChallenge = generateCodeChallenge(codeVerifier) // Store codeVerifier in session for later use

3. Authorization Request

const authUrl = new URL("https://sso.alien-api.com/oauth/authorize") authUrl.searchParams.set("response_type", "code") authUrl.searchParams.set("client_id", "your-provider-address") authUrl.searchParams.set("redirect_uri", "https://your-app.com/callback") authUrl.searchParams.set("scope", "openid") authUrl.searchParams.set("state", generateRandomState()) authUrl.searchParams.set("code_challenge", codeChallenge) authUrl.searchParams.set("code_challenge_method", "S256") // Redirect user to authUrl.toString()

The user completes sign-in with their Alien App; the server then redirects back to your redirect_uri with code and state query parameters.

4. Token Exchange

async function exchangeCode(code: string, codeVerifier: string) { const response = await fetch("https://sso.alien-api.com/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "authorization_code", code, client_id: "your-provider-address", redirect_uri: "https://your-app.com/callback", code_verifier: codeVerifier, }), }) return response.json() // Returns: { access_token, id_token, refresh_token, expires_in, token_type } }

5. Refresh Token

async function refreshToken(refreshToken: string) { const response = await fetch("https://sso.alien-api.com/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: "your-provider-address", }), }) return response.json() }

6. Verify Token (UserInfo)

async function getUserInfo(accessToken: string) { const response = await fetch("https://sso.alien-api.com/oauth/userinfo", { headers: { Authorization: `Bearer ${accessToken}`, }, }) return response.json() // Returns: { sub: "user-session-address", aud: "your-provider-address" } }

7. Verify JWT Locally

import jwt from "jsonwebtoken" import jwksClient from "jwks-rsa" const client = jwksClient({ jwksUri: "https://sso.alien-api.com/oauth/jwks", cache: true, rateLimit: true, }) async function verifyToken(token: string): Promise<any> { const decoded = jwt.decode(token, { complete: true }) if (!decoded) throw new Error("Invalid token") const key = await client.getSigningKey(decoded.header.kid) const publicKey = key.getPublicKey() return jwt.verify(token, publicKey, { algorithms: ["RS256"], issuer: "https://sso.alien-api.com", audience: "your-provider-address", }) }

Python (Authlib)

from authlib.integrations.flask_client import OAuth oauth = OAuth() oauth.register( name='alien', client_id='your-provider-address', client_secret='', server_metadata_url='https://sso.alien-api.com/.well-known/openid-configuration', client_kwargs={ 'scope': 'openid', 'code_challenge_method': 'S256', 'token_endpoint_auth_method': 'none', }, ) # Login route @app.route('/login') def login(): redirect_uri = url_for('callback', _external=True) return oauth.alien.authorize_redirect(redirect_uri) # Callback route @app.route('/callback') def callback(): token = oauth.alien.authorize_access_token() user_info = oauth.alien.userinfo() # Store token and user_info in session return redirect('/dashboard')

Go (golang.org/x/oauth2)

package main import ( "context" "golang.org/x/oauth2" ) var oauth2Config = &oauth2.Config{ ClientID: "your-provider-address", ClientSecret: "", // Public client Endpoint: oauth2.Endpoint{ AuthURL: "https://sso.alien-api.com/oauth/authorize", TokenURL: "https://sso.alien-api.com/oauth/token", }, RedirectURL: "http://localhost:8080/callback", Scopes: []string{"openid"}, } // Generate auth URL with PKCE func getAuthURL(state string, codeVerifier string) string { return oauth2Config.AuthCodeURL( state, oauth2.S256ChallengeOption(codeVerifier), ) } // Exchange code for token func exchangeToken(ctx context.Context, code, codeVerifier string) (*oauth2.Token, error) { return oauth2Config.Exchange( ctx, code, oauth2.VerifierOption(codeVerifier), ) } // Refresh token func refreshToken(ctx context.Context, token *oauth2.Token) (*oauth2.Token, error) { tokenSource := oauth2Config.TokenSource(ctx, token) return tokenSource.Token() }

Environment Variables

The examples above read your provider address from one environment variable:

ALIEN_PROVIDER_ADDRESS=your-provider-address

Next Steps

Last updated on