iltio

If you want to route any user requests through your server take a look at the following guides and integrations. Backend integration allows to keep your APP_TOKEN private as well as use more secure cookies to store the USER_TOKEN on the client.

Node Backend Integration

If you want to keep your authentication token secret it's possible to route the authentication through your server. To do that simply redirect calls by adding the token.

const response = await fetch(`https://iltio.com/authenticate?name=${name}&token=${process.env.ILTIO_TOKEN}`)
const { error, token } = await response.json()

On the frontend make sure to configure the plugin to route authentication requests through your interface.

import { configure } from 'iltio'

configure({
  authenticateUrl: 'https://myserver.com/authenticate'
})

This approach is only to keep your app token secret. It's also possible to fully hide the interface behind your server by routing all requests through to your server. To do this configure a custom API url.

import { configure } from 'iltio'

configure({
  url: 'https://myserver.com/auth-api'
})

Apart from the /authenticate route also the /verify/poll and /verify/confirm routes are needed. Here you find a more detailed interface description.

Using the provided plugins authentication through iltio can be integrated into any Next.js application. This approach offers a couple of advantages especially the use of same-site and secure cookies. Any request will be routed through a Serverless function which then contacts the iltio service. While there shouldn't be andy noticable differences for the average user, the Next.js integration requires much more code and configuration than the client-side implementation. Keep in mind that authenticated pages that display user specific data will not profit much from server-side rendering and are also not indexed by any search engine.

The easiest way to discover how the Next.js integration works is to look at the example code. The main part of the integration is to create a Serverless function handler in the api directory. iltio exports this handler in the iltio/next package. This handler can be exported in any api path, but the filename needs to contain the following wildcard [...path].js / [...path].ts. This filename will pass the iltio routes over to the handler. The structure will match the default interface.

// File: pages/api/ANY/THING/[...path].ts
import { createHandler } from 'iltio/next'

export default createHandler({
  referrer?: string
  verifyPage?: string
  sameSite?: boolean | 'lax' | 'none' | 'strict'
  path?: string
  maxAge?: number
  secure?: boolean
  httpOnly?: boolean
})

By default sameSite, httpOnly and secure cookies will be used. The referrer is optional and by default the header from the client will be used.

The React part of displaying the authentication form is straight-forward as the regular Form can be used and also customized the same way.

import { useRouter } from 'next/router'
import { CookieStorage } from 'iltio'
import { Authentication } from 'iltio/react'

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

  return (
    <Authentication
      configuration={{
        url: '/api/authentication',
        token: 'demo',
        storage: CookieStorage,
      }}
      onSuccess={() => router.push('/user')}
    />
  )
}

As the storage option make sure to pass the CookieStorage and add the APP_TOKEN. Make sure the url points to where the [...path].js file is located. onSuccess anything is possible as the user is now authenticated.

The most complicated part of this integration is to configure the middleware appropriately. When there isn't yet any middleware create a middleware.js / middleware.ts file and paste the following code.

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { authorize } from 'iltio'

export async function middleware(request: NextRequest) {
  const route = request.nextUrl.pathname
  const token = request.cookies.get('auth-token')?.value
  const noToken = typeof token !== 'string' || token.length !== 64

  // Authentication protected page, redirects to login.
  if (route === '/user' && noToken) {
    return NextResponse.redirect(new URL('/', request.url))
  }

  // Login page, requires no authentication.
  if (route === '/' && noToken) {
    return NextResponse.next()
  }

  // Any other pages assumed to require authentication.
  if (route !== '/user' && route !== '/' && noToken) {
    return NextResponse.json({ error: 'Please authenticate.' })
  }

  // Authorize token to check validity.
  const { error, role } = await authorize(token)
  const isAuthorized = !error && role === 'user'

  // Redirect login form to user dashboard if logged in.
  if (route === '/' && isAuthorized) {
    return NextResponse.redirect(new URL('/user', request.url))
  }

  if (!isAuthorized) {
    return NextResponse.json({ error: 'Failed to authorize.' })
  }

  return NextResponse.next()
}

export const config = {
  // All routes protected except authentication and public routes.
  matcher: ['/', '/user', '/api/((?!authentication|public).*)'],
}

The middleware will make sure that pages are protected and only valid tokens can be used to access them. The middleware should not apply to requests routed through to the handler added before. These requests are ignored at the bottom as part of the last matcher configuration.

Custom Verification Page

Unless the verifyPage is configured in createHandler authentication through a confirmation link in the mail or text will still direct the user to iltio.com. To avoid this a page with the same functionality can be added. The page needs to be called verify.jsx / verify.tsx but can be placed anywhere. When placed in the root pass verifyPage: '/' or otherwise prefix with the full path.

While the implementation of this page is largely up to the developer check out this reference implementation used in the example. In order to redirect the short link coming from text messages the following redirect needs to be added to the next.config.js. Below example assumes that the page is placed in the root.

module.exports = {
  async rewrites() {
    return [
      {
        source: '/v/:path',
        destination: '/verify?token=:path',
      },
    ]
  },
}