0.0.18 • Published 6 months ago

@deskree/atomic-auth-sdk v0.0.18

Weekly downloads
-
License
MIT
Repository
-
Last release
6 months ago

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-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

// 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

  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

0.0.18

6 months ago

0.0.17

6 months ago

0.0.15

7 months ago

0.0.14

7 months ago

0.0.13

7 months ago

0.0.12

7 months ago

0.0.11

7 months ago

0.0.10

7 months ago

0.0.9

7 months ago

0.0.8

7 months ago

0.0.6

7 months ago

0.0.5

7 months ago

0.0.4

7 months ago

0.0.3

7 months ago

0.0.2

7 months ago

0.0.1

7 months ago