Authentication Flows
Implement login, logout, and signup flows using AuthScape's authService with PKCE support.
AuthScape provides a complete authentication flow using OAuth 2.0 Authorization Code with PKCE (Proof Key for Code Exchange).
authService Overview
The authService helper provides authentication methods:
javascript
import { authService } from 'authscape';// Available methodsauthService().login(redirectUri, dnsRecord, deviceId);authService().signUp(redirectUrl);authService().logout(redirectUri);authService().manageAccount();authService().inviteUsers(inviteRequests);authService().inviteUser(inviteRequest);
Login Flow
PKCE Implementation
AuthScape uses PKCE for secure authentication:
javascript
// From authService.jslogin: async (redirectUserUri = null, dnsRecord = null, deviceId = null) => {let state = "1234";// Store redirect URI for post-loginif (redirectUserUri != null) {localStorage.setItem("redirectUri", redirectUserUri);}// Generate PKCE verifier and challengelet verifier = authService().generateRandomString();var challenge = await authService().challenge_from_verifier(verifier);// Store verifier for token exchangewindow.localStorage.setItem("verifier", verifier);// Build authorization URLlet redirectUri = window.location.origin + "/signin-oidc";let loginUri = process.env.authorityUri +"/connect/authorize" +"?response_type=code" +"&state=" + state +"&client_id=" + process.env.client_id +"&scope=email%20openid%20offline_access%20profile%20api1" +"&redirect_uri=" + redirectUri +"&code_challenge=" + challenge +"&code_challenge_method=S256";// Redirect to IDPwindow.location.href = loginUri;}
Using Login
jsx
import { authService } from 'authscape';export default function LoginPage() {const handleLogin = () => {// Basic login - redirect back to current page afterauthService().login();};const handleLoginWithRedirect = () => {// Login and redirect to specific pageauthService().login('/dashboard');};return (<div><h1>Welcome</h1><button onClick={handleLogin}>Sign In</button><button onClick={handleLoginWithRedirect}>Sign In to Dashboard</button></div>);}
Handling the Callback
Create a callback page at /signin-oidc:
jsx
// pages/signin-oidc.jsimport { useEffect } from 'react';import { useRouter } from 'next/router';import Cookies from 'js-cookie';export default function SignInCallback() {const router = useRouter();useEffect(() => {const handleCallback = async () => {const code = new URLSearchParams(window.location.search).get('code');const verifier = localStorage.getItem('verifier');if (!code || !verifier) {router.push('/');return;}// Exchange code for tokensconst response = await fetch(process.env.NEXT_PUBLIC_AUTHORITY_URI + '/connect/token', {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded',},body: new URLSearchParams({grant_type: 'authorization_code',code: code,redirect_uri: window.location.origin + '/signin-oidc',client_id: process.env.NEXT_PUBLIC_CLIENT_ID,code_verifier: verifier,}),});const tokens = await response.json();if (tokens.access_token) {// Store tokens in cookiesconst domainHost = window.location.hostname.split('.').slice(-2).join('.');Cookies.set('access_token', tokens.access_token, {path: '/',domain: domainHost,secure: true,sameSite: 'None'});Cookies.set('refresh_token', tokens.refresh_token, {path: '/',domain: domainHost,secure: true,sameSite: 'None'});Cookies.set('expires_in', tokens.expires_in, {path: '/',domain: domainHost});// Clear verifierlocalStorage.removeItem('verifier');// Redirect to stored URI or homeconst redirectUri = localStorage.getItem('redirectUri') || '/';localStorage.removeItem('redirectUri');router.push(redirectUri);}};handleCallback();}, [router]);return <div>Signing in...</div>;}
Signup Flow
javascript
// From authService.jssignUp: (redirectUrl = null) => {let AuthUri = process.env.authorityUri;let url = "";if (redirectUrl == null) {url = AuthUri + "/Identity/Account/Register?returnUrl=" + window.location.href;localStorage.setItem("redirectUri", window.location.href);} else {url = AuthUri + "/Identity/Account/Register?returnUrl=" + redirectUrl;localStorage.setItem("redirectUri", redirectUrl);}window.location.href = url;}
Using Signup
jsx
import { authService } from 'authscape';export default function SignupButton() {const handleSignup = () => {// Redirect to registration pageauthService().signUp('/welcome');};return (<button onClick={handleSignup}>Create Account</button>);}
Logout Flow
javascript
// From authService.jslogout: async (redirectUri = null) => {let domainHost = window.location.hostname.split('.').slice(-2).join('.');let AuthUri = process.env.authorityUri;// Clear auth cookiesCookies.remove('access_token', { path: '/', domain: domainHost });Cookies.remove('refresh_token', { path: '/', domain: domainHost });Cookies.remove('expires_in', { path: '/', domain: domainHost });// Redirect to IDP logoutsetTimeout(() => {if (redirectUri == null) {window.location.href = AuthUri + "/connect/logout?redirect=" + window.location.href;} else {window.location.href = AuthUri + "/connect/logout?redirect=" + redirectUri;}}, 500);}
Using Logout
jsx
import { authService } from 'authscape';export default function LogoutButton() {const handleLogout = () => {// Logout and redirect to homeauthService().logout('/');};return (<button onClick={handleLogout}>Sign Out</button>);}
Token Refresh
Access tokens expire after 1 hour. Use refresh tokens to get new access tokens:
javascript
async function refreshToken() {const refreshToken = Cookies.get('refresh_token');if (!refreshToken) {// No refresh token, redirect to loginauthService().login();return;}const response = await fetch(process.env.NEXT_PUBLIC_AUTHORITY_URI + '/connect/token', {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded',},body: new URLSearchParams({grant_type: 'refresh_token',refresh_token: refreshToken,client_id: process.env.NEXT_PUBLIC_CLIENT_ID,}),});const tokens = await response.json();if (tokens.access_token) {const domainHost = window.location.hostname.split('.').slice(-2).join('.');Cookies.set('access_token', tokens.access_token, {path: '/',domain: domainHost,secure: true,sameSite: 'None'});Cookies.set('refresh_token', tokens.refresh_token, {path: '/',domain: domainHost,secure: true,sameSite: 'None'});return tokens.access_token;} else {// Refresh failed, redirect to loginauthService().login();}}
Authorization Component
Wrap your app with AuthorizationComponent for automatic auth handling:
jsx
import { AuthorizationComponent } from 'authscape';import { useState } from 'react';export default function App({ Component, pageProps }) {const [currentUser, setCurrentUser] = useState(null);const [userLoaded, setUserLoaded] = useState(false);return (<AuthorizationComponentisEnabled={true}setCurrentUser={setCurrentUser}userLoaded={(loaded) => setUserLoaded(loaded)}>{userLoaded && currentUser && (<Component {...pageProps} user={currentUser} />)}</AuthorizationComponent>);}
Environment Configuration
Set up environment variables:
env
# .env.localNEXT_PUBLIC_AUTHORITY_URI=https://auth.yourapp.comNEXT_PUBLIC_CLIENT_ID=your-client-idNEXT_PUBLIC_API_URI=https://api.yourapp.com
Cookie Configuration
AuthScape stores tokens in cookies for cross-subdomain access:
| Cookie | Purpose | Expiration |
|---|---|---|
access_token | API authentication | 1 hour |
refresh_token | Get new access token | Long-lived |
expires_in | Track token expiry | With access token |
Flow Diagram
text
User Frontend IDP API│ │ │ │├── Click Login ───────►│ │ ││ ├── Generate PKCE ────►│ ││ ├── Redirect ─────────►│ ││ │ │ ││◄────────── Show Login Form ─────────────────┤ ││ │ │ │├── Enter Credentials ──────────────────────►│ ││ │ │ ││◄────────── Redirect with code ──────────────┤ ││ │ │ ││ ├── Exchange code ────►│ ││ │◄─── Return tokens ──┤ ││ │ │ ││ ├── Store in cookies │ ││ │ │ ││ ├── API request ──────────────────────────────►││ │ (with access_token) ││ │◄───────────────────── Return data ─────────┤
Next Steps
- Claims & Identity - JWT token claims
- IDP Admin Overview - OpenIddict configuration
- API Service Call - Making authenticated API calls