@codecot/okta-zustand
v0.4.0
Published
A modern Okta authentication library for React using Zustand state management
Maintainers
Readme
@codecot/okta-zustand
A modern Okta authentication library for React using Zustand state management. This library provides a complete replacement for @okta/okta-react with better performance, simpler API, and no Context API dependencies.
Features
- 🚀 Built with Zustand for optimal performance
- 🔐 Full OAuth 2.0 / OIDC support with PKCE
- 🔄 Automatic token refresh
- 💾 Flexible token storage (localStorage, sessionStorage, cookies)
- 🎣 React hooks for easy integration
- 🛡️ Protected route components
- 🔁 Retry logic with exponential backoff
- 📦 TypeScript support
- 🎯 No Context API - works anywhere in your app
- 🔒 Advanced route protection with group-based authorization
- 📡 Cross-tab synchronization
- 🎨 Multiple UI components for different use cases
Installation
npm install @codecot/okta-zustand zustand react react-router-dom
# or
yarn add @codecot/okta-zustand zustand react react-router-domQuick Start
1. Initialize Okta
import { OktaProvider } from '@codecot/okta-zustand'
const oktaConfig = {
issuer: 'https://your-okta-domain/oauth2/default',
clientId: 'your-client-id',
redirectUri: window.location.origin + '/login/callback',
scopes: ['openid', 'profile', 'email'],
pkce: true,
tokenManager: {
autoRenew: true,
storage: 'localStorage'
}
}
function App() {
return (
<OktaProvider config={oktaConfig}>
<Router>
{/* Your app routes */}
</Router>
</OktaProvider>
)
}2. Setup Routes
import { SecureRoute, LoginCallback } from '@codecot/okta-zustand'
function AppRoutes() {
return (
<Routes>
<Route path="/login/callback" element={<LoginCallback />} />
<Route
path="/protected"
element={
<SecureRoute>
<ProtectedPage />
</SecureRoute>
}
/>
<Route path="/login" element={<LoginPage />} />
</Routes>
)
}3. Use Authentication
import { useOktaAuth } from '@codecot/okta-zustand'
function LoginPage() {
const { login, isAuthenticated } = useOktaAuth()
if (isAuthenticated) {
return <Navigate to="/protected" />
}
return (
<button onClick={() => login()}>
Login with Okta
</button>
)
}
function UserProfile() {
const { user, logout, isLoading } = useOktaAuth()
if (isLoading) return <div>Loading...</div>
return (
<div>
<h1>Welcome {user?.name}!</h1>
<button onClick={() => logout()}>Logout</button>
</div>
)
}Documentation
- 📚 Component Documentation - Comprehensive guide to all available components
- 🏗️ Architecture Guide - Deep dive into the library architecture
- 🔄 Migration Guide - Migrating from @okta/okta-react
API Reference
Hooks
useOktaAuth()
Main hook for authentication operations.
const {
isAuthenticated, // boolean
isLoading, // boolean
user, // OktaUser | null
tokens, // OktaTokens | null
error, // AuthError | null
login, // (options?: LoginOptions) => Promise<void>
logout, // (options?: LogoutOptions) => Promise<void>
handleRedirectCallback, // () => Promise<void>
refreshToken // () => Promise<void>
} = useOktaAuth()useAccessToken()
Get the current access token.
const accessToken = useAccessToken() // string | nulluseIdToken()
Get the current ID token.
const idToken = useIdToken() // string | nulluseOktaUser()
Get the current user information.
const user = useOktaUser() // OktaUser | nulluseUserInfo()
Fetch detailed user information from the userinfo endpoint.
const { userInfo, loading, error } = useUserInfo()Components
<OktaProvider>
Initializes the Okta authentication system.
<OktaProvider
config={oktaConfig}
onAuthRequired={() => console.log('Auth required')}
>
{children}
</OktaProvider><SecureRoute>
Protects routes that require authentication.
<SecureRoute
redirectTo="/login"
loadingComponent={<CustomLoader />}
>
<ProtectedContent />
</SecureRoute><LoginCallback>
Handles the OAuth callback after login.
<LoginCallback
redirectTo="/dashboard"
loadingComponent={<div>Authenticating...</div>}
errorComponent={(error) => <ErrorPage error={error} />}
/><AuthGuard>
Conditionally renders content based on auth state.
<AuthGuard
fallback={<LoginPrompt />}
loadingComponent={<Spinner />}
>
<ProtectedContent />
</AuthGuard>Higher-Order Components
withOktaAuth()
Injects auth props into components.
const EnhancedComponent = withOktaAuth(MyComponent, {
requireAuth: true,
redirectTo: '/login'
})Advanced Usage
Custom Token Storage
import { TokenStorage } from '@codecot/okta-zustand'
class CustomStorage extends TokenStorage {
constructor() {
super('cookie', 'my-app-tokens')
}
}
const oktaConfig = {
// ... other config
tokenManager: {
storage: new CustomStorage()
}
}Error Handling
import { useOktaAuth, isAuthError, OktaAuthError } from '@codecot/okta-zustand'
function LoginForm() {
const { login, error } = useOktaAuth()
const handleLogin = async () => {
try {
await login()
} catch (err) {
if (isAuthError(err)) {
console.error('Auth error:', err.errorCode, err.errorSummary)
}
}
}
return (
<>
{error && <div className="error">{error.errorSummary}</div>}
<button onClick={handleLogin}>Login</button>
</>
)
}Manual Token Management
import { useAuthStore } from '@codecot/okta-zustand'
function TokenManager() {
const { tokens, refreshToken, setTokens } = useAuthStore()
const handleRefresh = async () => {
try {
await refreshToken()
} catch (error) {
console.error('Failed to refresh token:', error)
}
}
return (
<div>
<p>Access Token expires at: {new Date(tokens?.accessToken?.expiresAt * 1000).toLocaleString()}</p>
<button onClick={handleRefresh}>Refresh Token</button>
</div>
)
}Using with Next.js
// pages/_app.tsx
import { OktaProvider } from '@codecot/okta-zustand'
import { useRouter } from 'next/router'
function MyApp({ Component, pageProps }) {
const router = useRouter()
const oktaConfig = {
issuer: process.env.NEXT_PUBLIC_OKTA_ISSUER,
clientId: process.env.NEXT_PUBLIC_OKTA_CLIENT_ID,
redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/callback`,
}
return (
<OktaProvider
config={oktaConfig}
onAuthRequired={() => router.push('/login')}
>
<Component {...pageProps} />
</OktaProvider>
)
}Configuration Options
interface OktaConfig {
// Required
issuer: string
clientId: string
redirectUri: string
// Optional
scopes?: string[] // Default: ['openid', 'profile', 'email']
pkce?: boolean // Default: true
responseType?: string // Default: 'code'
responseMode?: string // Default: 'query'
postLogoutRedirectUri?: string
prompts?: string[]
display?: string
maxAge?: number
acrValues?: string
tokenManager?: {
autoRenew?: boolean // Default: true
expireEarlySeconds?: number // Default: 30
storage?: 'localStorage' | 'sessionStorage' | 'cookie'
storageKey?: string // Default: 'okta-token-storage'
}
}Migration from @okta/okta-react
1. Replace the Security component:
// Old
import { Security } from '@okta/okta-react'
<Security oktaAuth={oktaAuth}>
// New
import { OktaProvider } from '@codecot/okta-zustand'
<OktaProvider config={oktaConfig}>2. Replace SecureRoute:
// Old
import { SecureRoute } from '@okta/okta-react'
<SecureRoute path="/protected" component={ProtectedComponent} />
// New
import { SecureRoute } from '@codecot/okta-zustand'
<Route path="/protected" element={<SecureRoute><ProtectedComponent /></SecureRoute>} />3. Replace useOktaAuth hook:
// Old
import { useOktaAuth } from '@okta/okta-react'
const { oktaAuth, authState } = useOktaAuth()
// New
import { useOktaAuth } from '@codecot/okta-zustand'
const { isAuthenticated, user, login, logout } = useOktaAuth()Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues and feature requests, please use the GitHub issue tracker.
