@arascorp/auth
v1.0.0
Published
Reusable OAuth0/2/OIDC utility with inactivity timeout, built on oidc-client-ts, for React and Web Components.
Readme
@arascorp/auth
A modern authentication library for React applications, built on oidc-client-ts with enhanced features for seamless OAuth2/OIDC integration.
Features
- 🔐 OAuth2/OIDC Authentication - Built on oidc-client-ts v2.1.1
- ⚛️ React Integration - Hooks, context, and HOC patterns
- 🛡️ Protected Routes - Automatic authentication and return URL handling
- ⏱️ Inactivity Timeout - Configurable automatic logout
- 🔄 Auto Callback Handling - Automatic OAuth callback processing
- 🧪 TypeScript Support - Full TypeScript definitions
- 🌐 Environment Helper - Cross-bundler environment variable support
Installation
npm install @arascorp/authPeer Dependencies:
react^17.0.0 || ^18.0.0 || ^19.0.0react-dom^17.0.0 || ^18.0.0 || ^19.0.0react-router-dom^6.0.0 || ^7.0.0
Quick Start
1. Setup AuthProvider
import { BrowserRouter } from "react-router-dom";
import { AuthProvider, createAuthConfigFromEnv } from "@arascorp/auth";
// Load from environment variables
const authConfig = createAuthConfigFromEnv();
function App() {
return (
<BrowserRouter>
<AuthProvider config={authConfig}>
<YourAppContent />
</AuthProvider>
</BrowserRouter>
);
}⚠️ Important: AuthProvider must be inside a React Router context as it uses useNavigate().
2. Configure Environment Variables
# Vite (.env)
VITE_ARAS_AUTH_AUTHORITY=https://your-provider.com
VITE_ARAS_AUTH_CLIENT_ID=your-client-id
VITE_ARAS_AUTH_REDIRECT_URI=http://localhost:3000/callback
VITE_ARAS_AUTH_POST_LOGOUT_REDIRECT_URI=http://localhost:3000
VITE_ARAS_AUTH_SCOPE=openid profile email
VITE_ARAS_AUTH_AUDIENCE=https://your-api.com
VITE_ARAS_AUTH_TENANT=your-tenant-id
VITE_ARAS_AUTH_SILENT_REDIRECT_URI=http://localhost:3000/silent-renew
VITE_ARAS_AUTH_LOGOUT_AFTER_INACTIVITY_MS=900000
# Webpack/CRA (.env)
REACT_APP_ARAS_AUTH_AUTHORITY=https://your-provider.com
REACT_APP_ARAS_AUTH_CLIENT_ID=your-client-id
REACT_APP_ARAS_AUTH_REDIRECT_URI=http://localhost:3000/callback
REACT_APP_ARAS_AUTH_POST_LOGOUT_REDIRECT_URI=http://localhost:3000
REACT_APP_ARAS_AUTH_SCOPE=openid profile email
REACT_APP_ARAS_AUTH_AUDIENCE=https://your-api.com
REACT_APP_ARAS_AUTH_TENANT=your-tenant-id
REACT_APP_ARAS_AUTH_SILENT_REDIRECT_URI=http://localhost:3000/silent-renew
REACT_APP_ARAS_AUTH_LOGOUT_AFTER_INACTIVITY_MS=900000Or configure manually:
const authConfig = {
authority: "https://your-oidc-provider.com",
client_id: "your-client-id",
redirect_uri: window.location.origin + "/callback",
scope: "openid profile email",
tenant: "your-tenant-id" // Optional: ARAS CIAM tenant
};3. Use Authentication
import { useAuth } from "@arascorp/auth";
function UserProfile() {
const { user, loading, isAuthenticated, signInRedirect, signOutRedirect } = useAuth();
if (loading) return <div>Loading...</div>;
return isAuthenticated ? (
<div>
<h2>Welcome, {user?.profile.name}!</h2>
<button onClick={signOutRedirect}>Sign Out</button>
</div>
) : (
<button onClick={signInRedirect}>Sign In</button>
);
}4. Protect Routes
import { Routes, Route } from "react-router-dom";
import { ProtectedRoute } from "@arascorp/auth";
function AppRoutes() {
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
);
}Configuration Options
interface AuthConfig {
authority: string; // OIDC provider URL (required)
client_id: string; // Client ID (required)
redirect_uri?: string; // Callback URL - can be any path (default: window.location.origin + "/callback")
post_logout_redirect_uri?: string; // Default: window.location.origin
scope?: string; // Default: "openid profile email"
response_type?: string; // Default: "code"
audience?: string; // API audience (Auth0, etc.)
tenant?: string; // ARAS CIAM tenant identifier
logoutAfterInactivityMs?: number; // Default: 1800000 (30 mins), 0 to disable
}Callback Route: The library automatically detects your OAuth callback route from redirect_uri. You can use any path - not just /callback. Examples:
redirect_uri: "https://myapp.com/callback"→ handles callbacks on/callbackredirect_uri: "https://myapp.com/auth/return"→ handles callbacks on/auth/returnredirect_uri: "https://myapp.com/oauth/complete"→ handles callbacks on/oauth/complete
## Supported Providers
Works with any OIDC-compliant provider:
### Auth0
```tsx
const config = {
authority: "https://your-domain.auth0.com",
client_id: "your-client-id",
audience: "https://your-api.com" // Optional: for API access
};Azure AD B2C
const config = {
authority: "https://yourtenant.b2clogin.com/yourtenant.onmicrosoft.com/B2C_1_signin/v2.0",
client_id: "your-client-id"
};Okta
const config = {
authority: "https://your-org.okta.com/oauth2/default",
client_id: "your-client-id"
};Keycloak
const config = {
authority: "https://your-keycloak.com/auth/realms/your-realm",
client_id: "your-client-id"
};Advanced Patterns
Error Handling
The library provides comprehensive error handling for OAuth callback errors (e.g., access_denied, unauthorized_client, etc.) with built-in loop prevention.
Callback Error Handling Modes
The callbackErrorHandling prop on AuthProvider controls how OAuth callback errors are handled and displayed:
Available modes:
| Mode | UI Display | Use Case |
| --------------- | ------------------- | ------------------------------------------------- |
| "simple" | Plain text | Default - minimal unstyled error (⭐ default) |
| "silent" | None | Manual error handling via useAuth().error |
| "rich" | Styled with buttons | Production-ready error UI |
| "none" | None | ⚠️ Debugging only - disables loop prevention |
| Custom function | Your renderer | Full control over error display |
All modes except "none" prevent infinite redirect loops when all routes are protected.
Example - Simple mode (default):
<AuthProvider config={authConfig}>
<App />
</AuthProvider>
// Shows plain text error on callback route:
// "Authentication Failed
// Access denied. You don't have permission to access this application.
// [Try Again]"Example - Rich mode (recommended for production):
<AuthProvider config={authConfig} callbackErrorHandling="rich">
<App />
</AuthProvider>
// Shows styled error with branded UI and retry/dismiss buttonsExample - Silent mode (manual handling):
<AuthProvider config={authConfig} callbackErrorHandling="silent">
<App />
</AuthProvider>;
function App() {
const { error, clearError, signInRedirect } = useAuth();
return (
<div>
{error && (
<div className="error-banner">
<h3>{error.title}</h3>
<p>{error.message}</p>
{error.canRetry && <button onClick={signInRedirect}>Try Again</button>}
<button onClick={clearError}>Dismiss</button>
</div>
)}
<YourAppContent />
</div>
);
}Example - Custom function:
<AuthProvider
config={authConfig}
callbackErrorHandling={(error, clearError, signInRedirect) => (
<div className="my-custom-error">
<h2>🔒 {error.title}</h2>
<p>{error.message}</p>
{error.canRetry && <button onClick={signInRedirect}>Try Again</button>}
<button onClick={clearError}>Dismiss</button>
</div>
)}
>
<App />
</AuthProvider>Common OAuth errors handled:
access_denied- User denied access or lacks permission (no retry)unauthorized_client- Application not authorizedinvalid_request- Malformed authentication requestinvalid_scope- Invalid or unsupported scopeserver_error- Server-side error occurred (can retry)temporarily_unavailable- Service temporarily down (can retry)interaction_required- User interaction neededlogin_required- Re-authentication requiredconsent_required- User consent neededunsupported_response_type- Response type not supported
Error object structure:
interface AuthErrorInfo {
title: string; // User-friendly title
message: string; // Detailed error message
error: string; // OAuth error code (e.g., "access_denied")
canRetry: boolean; // Whether retry is appropriate
isUserAction?: boolean; // True if user explicitly denied access
}Testing error handling:
Navigate to the callback URL with error parameters:
http://localhost:3000/callback?error=access_denied&error_description=No%20access&state=testHow loop prevention works:
When an OAuth error occurs on the callback route:
AuthProviderdetects the error and displays it (based oncallbackErrorHandlingmode)- Children components (including
ProtectedRoute) are not rendered during error display - User can retry (triggers fresh sign-in) or reload the page (clears error state)
- This prevents infinite redirect loops even when all routes (including
/) are protected
Custom Loading States
import { useAuth } from "@arascorp/auth";
function MyComponent() {
const { user, loading, isAuthenticated, signInRedirect } = useAuth();
if (loading) {
return <CustomLoadingSpinner />;
}
if (!isAuthenticated) {
return (
<div>
<h2>Please Sign In</h2>
<button onClick={signInRedirect}>Sign In</button>
</div>
);
}
return <div>Welcome, {user?.profile.name}!</div>;
}Custom Fallback for ProtectedRoute
const AuthRequired = () => (
<div>
<h2>Authentication Required</h2>
<button onClick={() => signInRedirect()}>Sign In</button>
</div>
);
<ProtectedRoute fallbackComponent={AuthRequired}>
<ProtectedContent />
</ProtectedRoute>;HOC Pattern
import { withAuth, AuthComponentProps } from "@arascorp/auth";
function UserComponent({ user, isAuthenticated, signInRedirect }: AuthComponentProps) {
return isAuthenticated ? (
<div>Welcome, {user?.profile.name}!</div>
) : (
<button onClick={signInRedirect}>Sign In</button>
);
}
// Basic usage
const User = withAuth(UserComponent);
// With route protection
const ProtectedUser = withAuth(UserComponent, { requireAuth: true });
// With custom fallback
const CustomUser = withAuth(UserComponent, {
requireAuth: true,
fallbackComponent: () => <div>Loading...</div>
});Manual Callback Handling
By default, AuthProvider automatically handles OAuth callbacks on the route specified in redirect_uri.
Automatic mode (default):
<AuthProvider config={config} callbackErrorHandling="simple">
{/* Library automatically:
- Detects callback route from redirect_uri
- Processes OAuth response (code or error)
- Handles errors based on callbackErrorHandling
- Navigates to returnUrl or "/"
*/}
<YourApp />
</AuthProvider>Note: The library automatically detects your callback route from the redirect_uri configuration. You can use any path (not just /callback):
const config = {
authority: "https://your-provider.com",
client_id: "your-client-id",
redirect_uri: window.location.origin + "/auth/return" // Custom callback path
// ... other config
};
<AuthProvider config={config}>
{/* Auto-handles callback on /auth/return */}
<YourApp />
</AuthProvider>;Manual Control
For full control over callback processing, set autoHandleCallback={false}:
⚠️ Important: When autoHandleCallback={false}:
- You must create a callback route component
- You must manually call
authService.signinRedirectCallback() - You're responsible for storing/retrieving returnUrl
- You can optionally use library's error handling (see example below)
<AuthProvider config={config} autoHandleCallback={false}>
<Routes>
<Route path="/callback" element={<CallbackHandler />} />
{/* Your other routes */}
</Routes>
</AuthProvider>Manual callback handler with custom error handling:
// CallbackHandler.tsx - Custom error UI
import { useAuth, handleOAuthCallback } from "@arascorp/auth";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
function CallbackHandler() {
const { authService } = useAuth();
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
useEffect(() => {
handleOAuthCallback(authService, navigate, {
onError: (err) => setError(err.message)
});
}, [authService, navigate]);
if (error) {
return (
<div>
<h2>Authentication Error</h2>
<p>{error}</p>
<button onClick={() => navigate("/")}>Go Home</button>
</div>
);
}
return <div>Processing login...</div>;
}Manual callback handler using library's error UI (recommended):
// CallbackHandler.tsx - Reuse library's error display
import { useAuth, handleOAuthCallback } from "@arascorp/auth";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
function CallbackHandler() {
const { authService, setError } = useAuth();
const navigate = useNavigate();
useEffect(() => {
handleOAuthCallback(authService, navigate, { setError });
}, [authService, setError, navigate]);
// Error automatically displayed by AuthProvider based on callbackErrorHandling
return <div>Processing login...</div>;
}Simplest manual callback:
// CallbackHandler.tsx - Minimal implementation
import { useAuth, handleOAuthCallback } from "@arascorp/auth";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
function CallbackHandler() {
const { authService } = useAuth();
const navigate = useNavigate();
useEffect(() => {
handleOAuthCallback(authService, navigate).catch(console.error);
}, [authService, navigate]);
return <div>Processing login...</div>;
}
```**Why use manual mode?**
- Custom error UI/logic beyond built-in `callbackErrorHandling`
- Integration with existing error handling systems
- Analytics/logging on callback success/failure
- Custom navigation logic (e.g., based on user roles)
- Non-standard OAuth flows
React Strict Mode
The library is fully compatible with React Strict Mode. OAuth callbacks are processed only once, even with double-mounting, using sessionStorage-based state management.
TypeScript
Full TypeScript support with type definitions:
import { User } from "oidc-client-ts";
import { AuthConfig, AuthContextType, AuthComponentProps } from "@arascorp/auth";
// Typed hook usage
const { user, loading, isAuthenticated }: AuthContextType = useAuth();
// Typed HOC props
interface MyProps extends AuthComponentProps {
customProp: string;
}API Reference
useAuth()
Hook that provides authentication state and methods:
const {
user, // User | null - Current user object
loading, // boolean - Loading state
isAuthenticated, // boolean - Authentication status
error, // AuthErrorInfo | null - OAuth callback error
signInRedirect, // () => Promise<void> - Initiate sign-in
signOutRedirect, // () => Promise<void> - Sign out user
clearError, // () => void - Clear current error
setError, // (error: AuthErrorInfo | null) => void - Set error (for manual mode)
authService // AuthService - Direct service access
} = useAuth();<AuthProvider>
Main provider component:
<AuthProvider
config={authConfig} // Auth configuration (required)
callbackErrorHandling="simple" // Error display mode (default: "simple")
autoHandleCallback={true} // Auto-handle OAuth callbacks (default: true)
>
<App />
</AuthProvider>Props:
config(required) - Authentication configuration objectauthority(required) - OIDC provider URLclient_id(required) - Application client IDredirect_uri- Callback URL (default:window.location.origin + "/callback")scope- OAuth scopes (default:"openid profile email")logoutAfterInactivityMs- Auto-logout timeout in ms (default: 900000 = 15 min)audience- API audience (Auth0, etc.)tenant- Tenant ID (ARAS CIAM)
callbackErrorHandling- How to handle OAuth callback errors (default:"simple")"simple"- Show plain text error"silent"- No UI, access viauseAuth().error"rich"- Styled error with retry/dismiss buttons"none"- No error handling (⚠️ debugging only)- Custom function:
(error: AuthErrorInfo, clearError: () => void, signInRedirect: () => void) => React.ReactNode
autoHandleCallback- Automatically process OAuth callbacks (default:true)
### `<ProtectedRoute>`
Component that requires authentication:
```tsx
<ProtectedRoute fallbackComponent={CustomComponent}>
<YourProtectedContent />
</ProtectedRoute>withAuth()
HOC for injecting auth props:
withAuth(Component, {
requireAuth?: boolean, // Auto-redirect if not authenticated
fallbackComponent?: React.FC // Custom loading/fallback component
});createAuthConfigFromEnv()
Creates config from environment variables:
const config = createAuthConfigFromEnv({
// Optional overrides
scope: "openid profile email custom"
});Manual Callback Utilities
Helper functions for manual callback handling (when autoHandleCallback={false}):
import {
hasOAuthParams,
isOAuthCallback,
isCallbackRoute,
getCallbackPath,
saveReturnUrl,
getReturnUrl,
clearReturnUrl,
navigateToReturnUrl
} from "@arascorp/auth";
// Check if URL has OAuth params (code/error + state)
hasOAuthParams(location.search); // true/false
// Check if current page is OAuth callback
isOAuthCallback(); // true/false
// Check if pathname matches callback route
isCallbackRoute(location.pathname, config.redirect_uri);
// Extract callback path from redirect_uri
getCallbackPath("https://example.com/auth/callback"); // "/auth/callback"
// Save URL to return to after authentication
saveReturnUrl("/dashboard");
saveReturnUrl(); // Saves current location
// Get saved return URL and clear it from storage
const url = getReturnUrl(); // Returns saved URL or "/"
const url = getReturnUrl("/home"); // Custom fallback
// Clear saved return URL without retrieving it
clearReturnUrl();
// Navigate to saved return URL using React Router (recommended)
navigateToReturnUrl(navigate); // Navigates to saved URL or "/"
navigateToReturnUrl(navigate, { replace: true }); // With navigation options
// Complete callback handler - handles everything in one call
handleOAuthCallback(authService, navigate); // Throws on error
handleOAuthCallback(authService, navigate, { setError }); // Use library error display (recommended)
handleOAuthCallback(authService, navigate, {
onError: (err) => setCustomError(err.message), // Custom error handling
callbackRoute: "/auth/callback", // Stay here on error
navigationOptions: { replace: true } // Success navigation options
});Error Types
interface AuthErrorInfo {
title: string; // User-friendly error title
message: string; // Detailed error message
error: string; // OAuth error code
canRetry: boolean; // Whether user can retry
isUserAction?: boolean; // If user explicitly denied access
}Important Notes
autoHandleCallback and callbackErrorHandling
callbackErrorHandlingonly works whenautoHandleCallback={true}(default)- When
autoHandleCallback={false}, you must handle errors manually in your callback component - See Manual Callback Handling for complete examples
Browser Support
- Modern browsers with ES2018+ support
- React 17+, 18+, 19+
- React Router DOM v6+, v7+
Troubleshooting
"Oops! redirect_uri" or Invalid Redirect URI Error
This error occurs when the redirect_uri in your application config doesn't match the allowed callback URLs configured in your OIDC provider (CIAM, Auth0, etc.).
Common causes:
Exact mismatch - URIs must match exactly, including:
- Protocol (
http://vshttps://) - Domain/port (
localhost:3000vslocalhost:5173) - Path (
/callbackvs/auth/callback) - Trailing slashes (
/callbackvs/callback/)
- Protocol (
Case sensitivity - Some providers are case-sensitive (
/Callbackvs/callback)Missing configuration - Redirect URI not added to provider's allowed list
How to fix:
- Check your app configuration:
// Make sure redirect_uri exactly matches what's configured in CIAM
const authConfig = {
authority: "https://your-provider.com",
client_id: "your-client-id",
redirect_uri: "https://myapp.com/callback" // ← Must match EXACTLY
// ...
};Check your OIDC provider settings:
- ARAS CIAM: Go to your application settings → Allowed Callback URLs
- Auth0: Dashboard → Applications → Your App → Settings → Allowed Callback URLs
- Azure AD: App registrations → Authentication → Redirect URIs
- Okta: Applications → Your App → General Settings → Login redirect URIs
Verify exact match:
# Your app config:
redirect_uri: "https://myapp.com/callback"
# CIAM allowed URLs must include:
https://myapp.com/callback ✅ Correct
https://myapp.com/callback/ ❌ Wrong (trailing slash)
https://myapp.com/auth ❌ Wrong (different path)
http://myapp.com/callback ❌ Wrong (http vs https)- For local development, add both:
http://localhost:3000/callback
http://localhost:5173/callback (if using Vite)- Common pitfall - Environment variables:
# Make sure your .env matches production config
VITE_ARAS_AUTH_REDIRECT_URI=https://myapp.com/callback
# Not:
VITE_ARAS_AUTH_REDIRECT_URI=https://myapp.com/callback/ ❌Testing locally:
- Use exact domain from environment variables
- Check browser console for the actual redirect_uri being used
- Verify provider's configuration includes your local URL
License
MIT
Support
For issues and questions: [email protected]
