UNPKG

@deskree/atomic-auth-sdk

Version:

Unified Ory Kratos authentication SDK for frontend projects

354 lines (277 loc) 9.52 kB
# Atomic Auth SDK A unified authentication SDK for React applications using Ory Kratos, with full support for Next.js App Router. ## Features - Session management with automatic refresh - React hooks and context for authentication state - Higher-Order Component (HOC) pattern for protected routes - API route protection for Next.js App Router - Email and phone verification support - TypeScript support - Secure token handling via Ory Kratos HTTP-only cookies ## Installation ```bash npm install @deskree/atomic-auth-sdk ``` ## Setup Environment Create a `.env.local` file at the root of your project with the following variables: ``` # Required: Ory Kratos public API URL NEXT_PUBLIC_ORY_SDK_URL=https://your-ory-instance.url # Optional: Login redirect path NEXT_PUBLIC_LOGIN_PATH=/auth/login ``` ## Quick Start with Next.js App Router ### 1. Set up Auth Configuration ```typescript // auth.ts import { createOryAuth } from "@deskree/atomic-auth-sdk" export const { AuthProvider, useAuth, withAuth, createAuthApi } = createOryAuth( { oryUrl: process.env.NEXT_PUBLIC_ORY_SDK_URL!, loginPath: process.env.NEXT_PUBLIC_LOGIN_PATH || "/auth/login", } ) ``` ### 2. Wrap Your App with AuthProvider ```typescript // app/layout.tsx "use client" import { AuthProvider } from "@/auth" export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <AuthProvider>{children}</AuthProvider> </body> </html> ) } ``` ## Authentication Features ### Access Authentication in Components ```typescript // app/components/UserProfile.tsx "use client" import { useAuth } from "@/auth" export function UserProfile() { const { user, logout, isAuthenticated, isLoading } = useAuth() if (isLoading) return <div>Loading...</div> if (!isAuthenticated) return <div>Not signed in</div> return ( <div> <p>Signed in as {user?.email}</p> <p>Verification status: {user?.verified ? "Verified" : "Not verified"}</p> <button onClick={logout}>Sign Out</button> </div> ) } ``` ## Session Management Ory Kratos automatically refreshes session cookies when they're about to expire. Our SDK takes advantage of this behavior by making periodic calls to the session endpoint: ```typescript // app/components/SessionInfo.tsx "use client" import { useAuth } from "@/auth" export function SessionInfo() { const { session, refreshSession } = useAuth() if (!session) return <div>No active session</div> return ( <div> <p>Session expires: {new Date(session.expires_at).toLocaleString()}</p> <button onClick={refreshSession}>Refresh Session Data</button> </div> ) } ``` The session refresh is handled internally by simply calling Ory Kratos's `toSession()` method, which not only checks the current session validity but also automatically extends the session cookie's expiration time if the session is valid. This approach is simple, reliable, and works seamlessly in both browser and server environments without requiring administrative permissions or user re-authentication. ## Verification Features ### Check Verification Status ```typescript // app/components/VerificationStatus.tsx "use client" import { useAuth } from "@/auth" export function VerificationStatus() { const { user, isVerified } = useAuth() if (!user) return <div>Not signed in</div> return ( <div> <p>Email verification: {isVerified ? "Verified" : "Not verified"}</p> </div> ) } ``` ### Start Verification Flow ```typescript // app/components/VerificationButton.tsx "use client" import { useAuth } from "@/auth" export function VerificationButton() { const { startVerification } = useAuth() const handleStartVerification = async () => { try { // Start email verification flow const { verificationUrl, flowId } = await startVerification({ returnTo: window.location.href, }) if (verificationUrl) { // Option 1: Redirect to verification flow UI window.location.href = verificationUrl // Option 2: Store the flowId for custom verification UI // sessionStorage.setItem('verification_flow_id', flowId) // router.push('/custom-verification') } } catch (error) { console.error("Verification error:", error) } } return <button onClick={handleStartVerification}>Verify Email</button> } ``` ### Send Verification Code ```typescript // app/components/VerificationCodeForm.tsx "use client" import { useState } from "react" import { useAuth } from "@/auth" export function VerificationCodeForm() { const [email, setEmail] = useState("") const [flowId, setFlowId] = useState("") const [codeSent, setCodeSent] = useState(false) const [code, setCode] = useState("") const [verificationStatus, setVerificationStatus] = useState("") const { startVerification, sendVerificationCode, verifyCode } = useAuth() const handleStartVerification = async () => { try { const { flowId } = await startVerification({}) if (flowId) { setFlowId(flowId) } } catch (error) { console.error("Error starting verification:", error) } } const handleSendCode = async () => { if (!email || !flowId) return try { const { success } = await sendVerificationCode(email, flowId) if (success) { setCodeSent(true) setVerificationStatus("Verification code sent to your email") } } catch (error) { console.error("Error sending code:", error) setVerificationStatus("Failed to send verification code") } } const handleVerifyCode = async () => { if (!code || !flowId) return try { const { success } = await verifyCode(flowId, code) if (success) { setVerificationStatus("Email verified successfully!") } else { setVerificationStatus("Invalid verification code") } } catch (error) { console.error("Error verifying code:", error) setVerificationStatus("Verification failed") } } return ( <div> {!flowId ? ( <button onClick={handleStartVerification}>Start Verification</button> ) : !codeSent ? ( <div> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter your email" /> <button onClick={handleSendCode}>Send Verification Code</button> </div> ) : ( <div> <input type="text" value={code} onChange={(e) => setCode(e.target.value)} placeholder="Enter verification code" /> <button onClick={handleVerifyCode}>Verify Code</button> </div> )} {verificationStatus && <p>{verificationStatus}</p>} </div> ) } ``` ## API Reference ### useAuth() Hook ```typescript const { // Authentication state isLoading: boolean, // Whether authentication state is loading or not isAuthenticated: boolean, // Whether user is authenticated user: SessionUser | null, // Current user information session: Session | null, // Full Ory session object isVerified: boolean, // Whether user is verified error: Error | null, // Any authentication error // Authentication actions login: (returnTo?: string) => Promise<void>, // Redirect to login logout: () => Promise<void>, // Log out user // Session management refreshSession: () => Promise<void>, // Refresh session data // Verification startVerification: (options: { returnTo?: string }) => Promise<{ verificationUrl: string | null, error: Error | null, flowId: string | null }>, sendVerificationCode: (email: string, flowId: string) => Promise<{ success: boolean, error: Error | null, flow: any }>, verifyCode: (flowId: string, code: string) => Promise<{ success: boolean, error: Error | null, flow: any }>, getVerificationFlow: (flowId: string) => Promise<{ flow: any, error: Error | null }>, checkVerification: () => Promise<boolean> // Check if user is verified } = useAuth() ``` ### SessionUser Type ```typescript interface SessionUser { id: string email: string verified: boolean // Whether email is verified name?: { first?: string last?: string } } ``` ## Security Considerations 1. **Token Storage**: Session tokens are handled by Ory Kratos with HTTP-only cookies, preventing JavaScript access. 2. **Auto-extension**: The auto-extension feature helps prevent session expiration during active usage. 3. **API Protection**: Use the `createAuthApi` middleware to ensure only authenticated users can access your API endpoints. 4. **Route Protection**: For client-side protection, use the useAuth hook's isAuthenticated property with useEffect and the router to handle redirection. ## How It Works The SDK leverages Ory Kratos's cookie-based session management: 1. **Authentication**: When a user logs in through Ory Kratos, a session cookie is set 2. **Session Validation**: The SDK validates the session by making requests to Kratos's `/sessions/whoami` endpoint 3. **Session Extension**: Sessions are automatically extended before they expire (if enabled) 4. **Local Storage**: Basic user info is stored in localStorage for quick access, but the actual authentication relies on secure HTTP-only cookies ## License MIT