@deskree/atomic-auth-sdk v0.0.18
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
npm install @deskree/atomic-auth-sdkSetup 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/loginQuick Start with Next.js App Router
1. Set up Auth Configuration
// 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
// 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
// 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:
// 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
// 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
// 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
// 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
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
interface SessionUser {
id: string
email: string
verified: boolean // Whether email is verified
name?: {
first?: string
last?: string
}
}Security Considerations
Token Storage: Session tokens are handled by Ory Kratos with HTTP-only cookies, preventing JavaScript access.
Auto-extension: The auto-extension feature helps prevent session expiration during active usage.
API Protection: Use the
createAuthApimiddleware to ensure only authenticated users can access your API endpoints.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:
- Authentication: When a user logs in through Ory Kratos, a session cookie is set
- Session Validation: The SDK validates the session by making requests to Kratos's
/sessions/whoamiendpoint - Session Extension: Sessions are automatically extended before they expire (if enabled)
- Local Storage: Basic user info is stored in localStorage for quick access, but the actual authentication relies on secure HTTP-only cookies
License
MIT