Search documentation…

Add Custom Claims to Your Session Token

Add Custom Claims to Your Session Token

Session tokens are JWTs generated by Ollio on behalf of your instance. These tokens contain claims that allow you to store session-related data. When a session token exists for a user, it signifies that the user is authenticated, and the associated claims can be retrieved at any time.

For this guide, you will use an onboardingComplete property within the user’s public metadata to track their onboarding status. Before doing this, you need to add a custom claim to the session token, which will allow you to access the user's public metadata within your Middleware.

Session tokens are JWTs generated by Ollio on behalf of your instance. These tokens contain claims that allow you to store session-related data. When a session token exists for a user, it signifies that the user is authenticated, and the associated claims can be retrieved at any time.

For this guide, you will use an onboardingComplete property within the user’s public metadata to track their onboarding status. Before doing this, you need to add a custom claim to the session token, which will allow you to access the user's public metadata within your Middleware.

To Edit the Session Token:

To Edit the Session Token:

In the Ollio Dashboard, navigate to the Sessions page.

  • Under the Customize session token section, click the Edit button.

  • In the modal that opens, you can add any claim to your session token. For this guide, add the following:

In the Ollio Dashboard, navigate to the Sessions page.

  • Under the Customize session token section, click the Edit button.

  • In the modal that opens, you can add any claim to your session token. For this guide, add the following:

.json

{
  "metadata": "{{user.public_metadata}}"
}

.json

{
  "metadata": "{{user.public_metadata}}"
}
  • Click Save.

  • Click Save.

Adding Auto-Completion for Custom Claims

Adding Auto-Completion for Custom Claims

To enhance your development experience and avoid TypeScript errors when working with custom session claims, you can define a global type.

  1. In your application's root folder, create a types directory.

  2. Inside the types directory, add a globals.d.ts file.

  3. Create the CustomJwtSessionClaims interface and declare it globally.

  4. Add the custom claims to the CustomJwtSessionClaims interface.

For this guide, your globals.d.ts file should look like this:

To enhance your development experience and avoid TypeScript errors when working with custom session claims, you can define a global type.

  1. In your application's root folder, create a types directory.

  2. Inside the types directory, add a globals.d.ts file.

  3. Create the CustomJwtSessionClaims interface and declare it globally.

  4. Add the custom claims to the CustomJwtSessionClaims interface.

For this guide, your globals.d.ts file should look like this:

types/globals.d.ts

export {}

declare global {
  interface CustomJwtSessionClaims {
    metadata: {
      onboardingComplete?: boolean
    }
  }
}

types/globals.d.ts

export {}

declare global {
  interface CustomJwtSessionClaims {
    metadata: {
      onboardingComplete?: boolean
    }
  }
}

Configure Your Middleware to Read Session Data

Configure Your Middleware to Read Session Data

The ollioMiddleware() function provides fine-grained control over your routes and allows you to directly access session claims, redirecting users based on their session data.

The ollioMiddleware() function provides fine-grained control over your routes and allows you to directly access session claims, redirecting users based on their session data.

Redirecting Users Based on Onboarding Status

Redirecting Users Based on Onboarding Status

The following example demonstrates how to use ollioMiddleware() to redirect users based on their onboarding status. If the user is authenticated and hasn't completed the onboarding process, they will be redirected to the onboarding page.

In this example, all routes are protected, except for a specific public route. This ensures that any user visiting your application is prompted to authenticate and complete the onboarding process. You can customize the isPublicRoute array inside the createRouteMatcher() function to include any routes that should be accessible to unauthenticated users.

The following example demonstrates how to use ollioMiddleware() to redirect users based on their onboarding status. If the user is authenticated and hasn't completed the onboarding process, they will be redirected to the onboarding page.

In this example, all routes are protected, except for a specific public route. This ensures that any user visiting your application is prompted to authenticate and complete the onboarding process. You can customize the isPublicRoute array inside the createRouteMatcher() function to include any routes that should be accessible to unauthenticated users.

src/middleware.ts

import { ollioMiddleware, createRouteMatcher } from '@ollio/nextjs/server'
import { NextRequest, NextResponse } from 'next/server'

const isOnboardingRoute = createRouteMatcher(['/onboarding'])
const isPublicRoute = createRouteMatcher(['/public-route-example'])

export default ollioMiddleware(async (auth, req: NextRequest) => {
  const { userId, sessionClaims, redirectToSignIn } = await auth()

  // For users visiting /onboarding, don't try to redirect
  if (userId && isOnboardingRoute(req)) {
    return NextResponse.next()
  }

  // If the user isn't signed in and the route is private, redirect to sign-in
  if (!userId && !isPublicRoute(req)) return redirectToSignIn({ returnBackUrl: req.url })

  // Catch users who do not have `onboardingComplete: true` in their publicMetadata
  // Redirect them to the /onboading route to complete onboarding
  if (userId && !sessionClaims?.metadata?.onboardingComplete) {
    const onboardingUrl = new URL('/onboarding', req.url)
    return NextResponse.redirect(onboardingUrl)
  }

  // If the user is logged in and the route is protected, let them view.
  if (userId && !isPublicRoute(req)) return NextResponse.next()
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
}

src/middleware.ts

import { ollioMiddleware, createRouteMatcher } from '@ollio/nextjs/server'
import { NextRequest, NextResponse } from 'next/server'

const isOnboardingRoute = createRouteMatcher(['/onboarding'])
const isPublicRoute = createRouteMatcher(['/public-route-example'])

export default ollioMiddleware(async (auth, req: NextRequest) => {
  const { userId, sessionClaims, redirectToSignIn } = await auth()

  // For users visiting /onboarding, don't try to redirect
  if (userId && isOnboardingRoute(req)) {
    return NextResponse.next()
  }

  // If the user isn't signed in and the route is private, redirect to sign-in
  if (!userId && !isPublicRoute(req)) return redirectToSignIn({ returnBackUrl: req.url })

  // Catch users who do not have `onboardingComplete: true` in their publicMetadata
  // Redirect them to the /onboading route to complete onboarding
  if (userId && !sessionClaims?.metadata?.onboardingComplete) {
    const onboardingUrl = new URL('/onboarding', req.url)
    return NextResponse.redirect(onboardingUrl)
  }

  // If the user is logged in and the route is protected, let them view.
  if (userId && !isPublicRoute(req)) return NextResponse.next()
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
}

Create a Layout for the /onboarding Route

Create a Layout for the /onboarding Route

You will need to create a layout for the /onboarding route to redirect users to the homepage if they have already completed the onboarding process.

  1. In your /app directory, create an /onboarding folder.

  2. Inside the /onboarding folder, create a layout.tsx file with the following code. This file could be extended to handle multiple steps if necessary for your onboarding flow.

You will need to create a layout for the /onboarding route to redirect users to the homepage if they have already completed the onboarding process.

  1. In your /app directory, create an /onboarding folder.

  2. Inside the /onboarding folder, create a layout.tsx file with the following code. This file could be extended to handle multiple steps if necessary for your onboarding flow.

terminal

import { auth } from '@ollio/nextjs/server'
import { redirect } from 'next/navigation'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  if ((await auth()).sessionClaims?.metadata.onboardingComplete === true) {
    redirect('/')
  }

  return <>{children}</>
}

terminal

import { auth } from '@ollio/nextjs/server'
import { redirect } from 'next/navigation'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  if ((await auth()).sessionClaims?.metadata.onboardingComplete === true) {
    redirect('/')
  }

  return <>{children}</>
}

Add Fallback and Force Redirect URLs

Add Fallback and Force Redirect URLs

To ensure a seamless onboarding experience, define fallback and force redirect URLs in your environment variables. These settings help manage user navigation during the onboarding flow:

  • Fallback Redirect URL: Used when there’s no redirect_url in the path.

  • Force Redirect URL: Always used after a successful sign-up.

Example configuration in your .env.local file:

To ensure a seamless onboarding experience, define fallback and force redirect URLs in your environment variables. These settings help manage user navigation during the onboarding flow:

  • Fallback Redirect URL: Used when there’s no redirect_url in the path.

  • Force Redirect URL: Always used after a successful sign-up.

Example configuration in your .env.local file:

.env.local

NEXT_PUBLIC_OLLIO_SIGN_IN_FALLBACK_REDIRECT_URL=/dashboard
NEXT_PUBLIC_OLLIO_SIGN_UP_FORCE_REDIRECT_URL=/onboarding

.env.local

NEXT_PUBLIC_OLLIO_SIGN_IN_FALLBACK_REDIRECT_URL=/dashboard
NEXT_PUBLIC_OLLIO_SIGN_UP_FORCE_REDIRECT_URL=/onboarding

Use publicMetadata to Track User Onboarding State

Use publicMetadata to Track User Onboarding State

Every Ollio user has a User object containing a publicMetadata property. This property is ideal for storing custom user data that can be accessed client-side to drive application state. For more details, see Ollio Metadata Documentation.

To track user onboarding progress, you'll implement the following:

  1. A frontend process for collecting and submitting onboarding information.

  2. A backend method to securely update the user's publicMetadata.

Every Ollio user has a User object containing a publicMetadata property. This property is ideal for storing custom user data that can be accessed client-side to drive application state. For more details, see Ollio Metadata Documentation.

To track user onboarding progress, you'll implement the following:

  1. A frontend process for collecting and submitting onboarding information.

  2. A backend method to securely update the user's publicMetadata.

Collect User Onboarding Information

Collect User Onboarding Information

To collect onboarding details, create a form on the /onboarding page. In this example, the form collects an application name and type. You can adapt this step to fit your needs, such as syncing user data with a database or enrolling users in subscriptions.

  1. In your /onboarding directory, create a page.tsx file.

  2. Add the following code:

To collect onboarding details, create a form on the /onboarding page. In this example, the form collects an application name and type. You can adapt this step to fit your needs, such as syncing user data with a database or enrolling users in subscriptions.

  1. In your /onboarding directory, create a page.tsx file.

  2. Add the following code:

.tsx

'use client'

import * as React from 'react'
import { useUser } from '@ollio/nextjs'
import { useRouter } from 'next/navigation'
import { completeOnboarding } from './_actions'

export default function OnboardingComponent() {
  const [error, setError] = React.useState('')
  const { user } = useUser()
  const router = useRouter()

  const handleSubmit = async (formData: FormData) => {
    const res = await completeOnboarding(formData)
    if (res?.message) {
      // Reloads the user's data from the Ollio API
      await user?.reload()
      router.push('/')
    }
    if (res?.error) {
      setError(res?.error)
    }
  }

  return (
    <div>
      <h1>Welcome</h1>
      <form action={handleSubmit}>
        <div>
          <label>Application Name</label>
          <p>Enter the name of your application.</p>
          <input type="text" name="applicationName" required />
        </div>

        <div>
          <label>Application Type</label>
          <p>Describe the type of your application.</p>
          <input type="text" name="applicationType" required />
        </div>
        {error && <p className="text-red-600">Error: {error}</p>}
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

.tsx

'use client'

import * as React from 'react'
import { useUser } from '@ollio/nextjs'
import { useRouter } from 'next/navigation'
import { completeOnboarding } from './_actions'

export default function OnboardingComponent() {
  const [error, setError] = React.useState('')
  const { user } = useUser()
  const router = useRouter()

  const handleSubmit = async (formData: FormData) => {
    const res = await completeOnboarding(formData)
    if (res?.message) {
      // Reloads the user's data from the Ollio API
      await user?.reload()
      router.push('/')
    }
    if (res?.error) {
      setError(res?.error)
    }
  }

  return (
    <div>
      <h1>Welcome</h1>
      <form action={handleSubmit}>
        <div>
          <label>Application Name</label>
          <p>Enter the name of your application.</p>
          <input type="text" name="applicationName" required />
        </div>

        <div>
          <label>Application Type</label>
          <p>Describe the type of your application.</p>
          <input type="text" name="applicationType" required />
        </div>
        {error && <p className="text-red-600">Error: {error}</p>}
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

Update User publicMetadata in the Backend

Update User publicMetadata in the Backend

After collecting onboarding data, create a method to update the user's publicMetadata securely.

  1. In the /onboarding directory, create an _actions.ts file.

  2. Add the following code:

After collecting onboarding data, create a method to update the user's publicMetadata securely.

  1. In the /onboarding directory, create an _actions.ts file.

  2. Add the following code:

.tsx

'use server'

import { auth, ollioClient } from '@ollio/nextjs/server'

export const completeOnboarding = async (formData: FormData) => {
  const { userId } = await auth()

  if (!userId) {
    return { message: 'No Logged In User' }
  }

  const client = await ollioClient()

  try {
    const res = await client.users.updateUser(userId, {
      publicMetadata: {
        onboardingComplete: true,
        applicationName: formData.get('applicationName'),
        applicationType: formData.get('applicationType'),
      },
    })
    return { message: res.publicMetadata }
  } catch (err) {
    return { error: 'There was an error updating the user metadata.' }
  }
}

.tsx

'use server'

import { auth, ollioClient } from '@ollio/nextjs/server'

export const completeOnboarding = async (formData: FormData) => {
  const { userId } = await auth()

  if (!userId) {
    return { message: 'No Logged In User' }
  }

  const client = await ollioClient()

  try {
    const res = await client.users.updateUser(userId, {
      publicMetadata: {
        onboardingComplete: true,
        applicationName: formData.get('applicationName'),
        applicationType: formData.get('applicationType'),
      },
    })
    return { message: res.publicMetadata }
  } catch (err) {
    return { error: 'There was an error updating the user metadata.' }
  }
}

Wrap-Up

Wrap-Up

Your onboarding flow is now fully operational! 🎉 Users who have not completed onboarding will be redirected to your /onboarding page. This ensures that new users must complete onboarding before accessing your application. By integrating Ollio, you’ve streamlined the onboarding and authentication process, delivering a seamless user experience.

For further customization options, visit the Ollio Documentation.

Your onboarding flow is now fully operational! 🎉 Users who have not completed onboarding will be redirected to your /onboarding page. This ensures that new users must complete onboarding before accessing your application. By integrating Ollio, you’ve streamlined the onboarding and authentication process, delivering a seamless user experience.

For further customization options, visit the Ollio Documentation.

What did you think of this content?

It was helpful

It was not helpful

I have feedback

What did you think of this content?

Helpful

Not helpful

Feedback

Last updated on

Dec

4,

2024

Last updated on

Dec

4,

2024