Skip to main content

Overview

The CallbackPage component handles the authentication callback after users are redirected back from Logto. It processes the authentication code, exchanges it for tokens, and manages the post-authentication flow including popup and redirect scenarios.

Installation

npm install @ouim/logto-authkit

Basic Usage

Create a callback route in your application:
app/callback/page.tsx
import { CallbackPage } from '@ouim/logto-authkit'

export default function Callback() {
  return <CallbackPage />
}

Props

className
string
Additional CSS classes to apply to the container element.
<CallbackPage className="bg-gray-50" />
loadingComponent
React.ReactNode
Custom component to display while processing authentication.
<CallbackPage
  loadingComponent={
    <div className="flex items-center gap-2">
      <Spinner />
      <p>Please wait...</p>
    </div>
  }
/>
successComponent
React.ReactNode
Custom component to display after successful authentication.
<CallbackPage
  successComponent={
    <div className="text-center">
      <CheckCircle className="text-green-500" />
      <p>Success! Taking you to your dashboard...</p>
    </div>
  }
/>
onSuccess
() => void
Callback function executed after successful authentication, before redirect/close.
<CallbackPage
  onSuccess={() => {
    console.log('Authentication successful!')
    // Track analytics, etc.
  }}
/>
onError
(error: Error) => void
Callback function executed if authentication fails.
<CallbackPage
  onError={error => {
    console.error('Auth failed:', error)
    // Show error message, redirect to error page, etc.
  }}
/>

How It Works

The CallbackPage component:
  1. Receives the auth code from Logto redirect URL
  2. Exchanges code for tokens using useHandleSignInCallback() from @logto/react
  3. Detects the flow type (popup vs. redirect)
  4. Handles completion:
    • Popup flow: Sends message to parent window and closes
    • Redirect flow: Redirects to home page (/)

Flow Detection

The component automatically detects whether it’s handling a popup or redirect flow:
const isPopup = (window.opener && window.opener !== window) || sessionStorage.getItem('simple_logto_popup_flow') === 'true'
  • Checks if the window has an opener (parent window)
  • Falls back to sessionStorage flag for cross-origin scenarios
For popup-based authentication:
  1. Component detects it’s in a popup
  2. Processes authentication
  3. Sends SIGNIN_SUCCESS message to parent window
  4. Closes the popup
// Sends message to parent
window.opener.postMessage({ type: 'SIGNIN_SUCCESS' }, window.location.origin)

// Closes popup after small delay
setTimeout(() => {
  window.close()
}, 100)

Fallback Mechanism

If window.opener is unavailable (some browsers clear it), falls back to localStorage:
localStorage.setItem('simple_logto_signin_complete', Date.now().toString())
The parent window listens for both postMessage and localStorage events to handle popup completion.

Redirect Flow

For full-page redirect authentication:
  1. Component detects it’s NOT in a popup
  2. Processes authentication
  3. Redirects to home page (/)
if (!isPopup) {
  window.location.href = '/'
}

Examples

Basic Setup (Next.js App Router)

app/callback/page.tsx
import { CallbackPage } from '@ouim/logto-authkit'

export default function Callback() {
  return <CallbackPage />
}

With Custom Loading State

import { CallbackPage } from '@ouim/logto-authkit'
import { Loader2 } from 'lucide-react'

export default function Callback() {
  return (
    <CallbackPage
      loadingComponent={
        <div className="flex flex-col items-center gap-4">
          <Loader2 className="h-8 w-8 animate-spin text-blue-500" />
          <p className="text-lg font-medium">Signing you in...</p>
          <p className="text-sm text-gray-500">Please wait a moment</p>
        </div>
      }
    />
  )
}

With Success Tracking

import { CallbackPage } from '@ouim/logto-authkit'
import { useRouter } from 'next/navigation'

export default function Callback() {
  const router = useRouter()

  return (
    <CallbackPage
      onSuccess={() => {
        // Track successful authentication
        if (typeof window !== 'undefined' && window.analytics) {
          window.analytics.track('User Signed In', {
            method: 'logto',
            timestamp: new Date().toISOString(),
          })
        }
      }}
      onError={error => {
        console.error('Authentication error:', error)
        // Redirect to error page
        router.push('/auth/error')
      }}
    />
  )
}

Custom Styling

import { CallbackPage } from '@ouim/logto-authkit'

export default function Callback() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-500 to-purple-600">
      <CallbackPage
        className="flex items-center justify-center min-h-screen"
        loadingComponent={
          <div className="bg-white rounded-lg shadow-xl p-8">
            <div className="flex items-center gap-3">
              <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500" />
              <p className="text-gray-700">Authenticating...</p>
            </div>
          </div>
        }
      />
    </div>
  )
}

With Custom Redirect

import { CallbackPage } from '@ouim/logto-authkit'

export default function Callback() {
  return (
    <CallbackPage
      onSuccess={() => {
        // Check if this is popup flow
        const isPopup = (window.opener && window.opener !== window) || sessionStorage.getItem('simple_logto_popup_flow') === 'true'

        if (!isPopup) {
          // Custom redirect for non-popup flow
          window.location.href = '/dashboard'
        }
      }}
    />
  )
}

Default UI

If no custom components are provided, the callback page displays:

Loading State

<div className="flex items-center gap-2">
  <Spinner /> {/* Animated spinner */}
  <div className="text-lg text-slate-500">Signing you in...</div>
</div>

Success State

<div className="flex items-center gap-2">
  <Spinner />
  <div className="text-lg text-slate-500">Authentication complete! Redirecting...</div>
</div>

Session Storage Flag

For popup flows, your sign-in page should set a flag:
app/signin/page.tsx
'use client'

import { useEffect } from 'react'
import { useAuth } from '@ouim/logto-authkit'
import { useSearchParams } from 'next/navigation'

export default function SignIn() {
  const { signIn } = useAuth()
  const searchParams = useSearchParams()
  const isPopup = searchParams.get('popup') === 'true'

  useEffect(() => {
    if (isPopup) {
      sessionStorage.setItem('simple_logto_popup_flow', 'true')
    }
    signIn('/callback')
  }, [signIn, isPopup])

  return <div>Redirecting to sign in...</div>
}

Error Handling

The component handles errors during authentication:
try {
  // Process authentication
} catch (error) {
  console.error('Authentication callback error:', error)
  if (onError) {
    onError(error as Error)
  }
}
Common errors:
  • Invalid authorization code
  • Token exchange failure
  • Network errors
  • CORS issues

Best Practices

Always use a dedicated route like /callback or /auth/callback for handling authentication callbacks.
Make sure your Logto application’s redirect URI matches your callback route exactly.
The component automatically handles both popup and redirect flows - no additional configuration needed.
Use the onError callback to track authentication failures in your monitoring system.
Provide custom loading and success components that match your application’s design.

Configuration in Logto

In your Logto application settings, add your callback URL:
https://yourdomain.com/callback
For local development:
http://localhost:3000/callback

Troubleshooting

Popup doesn’t close: Ensure your sign-in page sets the simple_logto_popup_flow sessionStorage flag when ?popup=true is in the URL.
Infinite redirect loop: Check that your Logto redirect URI exactly matches your callback route.
CORS errors: Ensure your Logto application’s allowed origins include your application’s domain.
The component automatically cleans up the simple_logto_popup_flow flag after successful authentication.

AuthProvider

Configure authentication provider

useAuth Hook

Access sign-in functionality

UserCenter

User menu with sign-in button

Route Protection

Protect authenticated routes