Pular para o conteúdo

Next.js

Use o authio-provider-nextauth em produção: Auth.js v5 ou NextAuth v4, escopos, claims tipadas, refresh tokens, rotas protegidas e login em popup.

O authio-provider-nextauth é o provider do Authio para o Auth.js v5 e o NextAuth v4, versão 2.1.0 no npm. Ele é construído sobre o provider OpenID Connect padrão de cada versão, então discovery, PKCE e validação do ID token vêm do próprio Auth.js; o pacote acrescenta os padrões do Authio, as claims tipadas e os helpers de popup.

Esta página continua de onde o Início rápido para: o ponto de entrada certo, mais escopos, sessões mais longas e rotas que só quem entrou alcança.

Escolha o ponto de entrada

Sua versão do next-authImporte deConstruído sobre
5.0.0-beta (Auth.js)authio-provider-nextauth/v5O tipo de provider oidc; lê as configurações do ambiente.
^4.24.0authio-provider-nextauthO tipo de provider oauth com discovery; toda configuração é passada no código.
Qualquer umaauthio-provider-nextauth/popupOs helpers de login em popup, que não importam nada do next-auth.

As duas versões exportam o mesmo provider Authio e o mesmo tipo AuthioProfile. O next-auth é uma peer dependency: instale a versão que você usa e fixe a beta do Auth.js em vez de seguir a tag beta, que pula entre versões incompatíveis.

Auth.js v5Bash
npm install authio-provider-nextauth next-auth@5.0.0-beta.32

Variáveis de ambiente

VariávelO que guarda
AUTH_AUTHIO_ISSUERO emissor do realm, como https://authio.api.hyperio.tech/realms/acme.
AUTH_AUTHIO_IDO ID do client, como acme-web.
AUTH_AUTHIO_SECRETO segredo do client. Deixe de fora num client público.
AUTH_SECRETA chave que cifra o cookie de sessão, gerada com npx auth secret. NEXTAUTH_SECRET no v4.
AUTH_URLO endereço público da aplicação, como https://app.example.com. NEXTAUTH_URL no v4.

O Auth.js v5 lê sozinho as três variáveis AUTH_AUTHIO_: ele coloca o ID do provider, authio, em maiúsculas e procura exatamente esses nomes. No v4, passe os valores para Authio() no código.

Duas variáveis que costumam ser confundidas

No v5, defina AUTH_URL, não NEXTAUTH_URL: só AUTH_URL (ou AUTH_TRUST_HOST=true) marca o host como confiável, e sem isso um deploy próprio falha com UntrustedHost. E AUTH_SECRET nunca é o segredo do client: com um valor só para os dois, girar o segredo do client derruba todas as sessões abertas.

Configure o Auth.js v5

Com as variáveis no lugar, o provider não precisa de credenciais no código. Acrescente a verificação de state: o ponto de entrada v5 não define checks, então o Auth.js fica só com o PKCE, e é o state que amarra o retorno ao pedido que o iniciou. As opções que você passar são mescladas sobre o ambiente.

auth.tsTypeScript
import NextAuth from 'next-auth'
import Authio from 'authio-provider-nextauth/v5'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [Authio({ checks: ['pkce', 'state'] })],
})

O redirect URI a registrar no client é https://app.example.com/api/auth/callback/authio, um por ambiente.

Configure o NextAuth v4

No v4, issuer, clientId e clientSecret são passados explicitamente; issuer é obrigatório, porque é dele que sai o endereço de discovery. O ponto de entrada já pede openid email profile, liga as verificações pkce e state e mapeia sub, name (ou preferred_username), email e picture para o usuário.

lib/auth.tsTypeScript
import type { NextAuthOptions } from 'next-auth'
import { Authio } from 'authio-provider-nextauth'

export const authOptions: NextAuthOptions = {
  providers: [
    Authio({
      issuer: process.env.AUTH_AUTHIO_ISSUER!,
      clientId: process.env.AUTH_AUTHIO_ID!,
      clientSecret: process.env.AUTH_AUTHIO_SECRET!,
    }),
  ],
}

Escopos

Os dois pontos de entrada pedem openid profile email. Todo escopo que você pedir também precisa estar liberado no client em Clients.

EscopoO que acrescenta
openidO ID token. Obrigatório, a menos que o client desligue Require OpenID Connect.
profile, email, phoneNomes e username; o endereço e se ele foi verificado; o telefone.
rolesOs papéis do usuário no access token, para a sua API conferir — veja Papéis e permissões.
offline_accessUm refresh token — veja Refresh tokens.

Sobrescreva authorization.params.scope para mudar a lista. As opções são mescladas chave por chave, mas scope é uma string única e é trocada por inteiro, então escreva a lista completa:

auth.tsTypeScript
Authio({
  checks: ['pkce', 'state'],
  authorization: {
    params: { scope: 'openid profile email roles offline_access' },
  },
})

Para chamar uma API sua registrada como servidor de recurso, acrescente o identificador dela, como https://api.example.com, como resource nos mesmos params. O access token passa a levá-lo em aud no lugar do padrão Authio, e as renovações o mantêm.

Claims tipadas

AuthioProfile tipa as claims do ID token que o Auth.js validou. O argumento profile do callback jwt só as traz no login, então copie para o token o que você precisar ali.

ClaimsQuandoO que dizem
subSempreO ID estável do usuário no realm.
name, given_name, family_name, preferred_username, email, email_verifiedEscopos profile e emailQuem a pessoa é.
sid, auth_time, acr, amrSempreA sessão, quando a pessoa se autenticou, com que força (1 a 3) e com quais métodos (pwd, otp, swk, fed, mfa…).
idp, org_id, org_aliasLogin federado ou para uma organizaçãoO alias do provedor externo; a organização da sessão.

As claims acrescentadas pelos claim mappers do seu client também chegam, tipadas como unknown pela assinatura de índice. Declare uma vez o que você guarda no token e na sessão:

types/next-auth.d.tsTypeScript
import type { DefaultSession } from 'next-auth'
import 'next-auth/jwt'

declare module 'next-auth' {
  interface Session {
    error?: string
    user: { username?: string } & DefaultSession['user']
  }
}

declare module 'next-auth/jwt' {
  interface JWT {
    username?: string
    accessToken?: string
    expiresAt?: number
    refreshToken?: string
    error?: string
  }
}
O que o callback session devolve é servido ao navegador por /api/auth/session. Coloque o access token ali só se código no navegador precisar chamar a sua API com ele.

Clients confidenciais e públicos

Uma aplicação Next.js troca o código no próprio servidor, então o comum é um client confidencial. Se o client for público (método none), declare isso: sem a declaração, o Auth.js ainda manda um cabeçalho Basic com a senha literal undefined, que o Authio ignora num client público — até o client virar confidencial. No v4, passe também clientSecret: '' para satisfazer os tipos.

auth.tsTypeScript
Authio({
  checks: ['pkce', 'state'],
  client: { token_endpoint_auth_method: 'none' },
})

Refresh tokens

O Authio só emite refresh_token quando os escopos concedidos incluem offline_access — nenhuma configuração de tempo de vida gera um. Libere o escopo no client, peça-o e renove o access token você mesmo no callback jwt:

auth.tsTypeScript
import NextAuth from 'next-auth'
import Authio from 'authio-provider-nextauth/v5'

async function refresh(refreshToken: string) {
  const credentials = btoa(`${process.env.AUTH_AUTHIO_ID}:${process.env.AUTH_AUTHIO_SECRET}`)
  const response = await fetch(`${process.env.AUTH_AUTHIO_ISSUER}/protocol/openid-connect/token`, {
    method: 'POST',
    headers: {
      authorization: `Basic ${credentials}`,
      'content-type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken }),
  })

  if (!response.ok) return null

  return (await response.json()) as {
    access_token: string
    expires_in: number
    refresh_token?: string
  }
}

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Authio({
      checks: ['pkce', 'state'],
      authorization: { params: { scope: 'openid profile email offline_access' } },
    }),
  ],
  callbacks: {
    async jwt({ token, account }) {
      if (account) {
        return {
          ...token,
          accessToken: account.access_token,
          expiresAt: account.expires_at,
          refreshToken: account.refresh_token,
        }
      }

      if (token.expiresAt && Date.now() < (token.expiresAt - 60) * 1000) return token

      const refreshed = token.refreshToken ? await refresh(token.refreshToken) : null

      if (!refreshed) return { ...token, error: 'RefreshTokenError' }

      return {
        ...token,
        accessToken: refreshed.access_token,
        expiresAt: Math.floor(Date.now() / 1000) + refreshed.expires_in,
        refreshToken: refreshed.refresh_token ?? token.refreshToken,
        error: undefined,
      }
    },
    async session({ session, token }) {
      session.error = token.error
      return session
    },
  },
})
  • Guarde o novo refresh token. Refresh tokens são rotacionados por padrão; o anterior fica gasto. Dentro da janela de reuso (10 segundos por padrão), uma renovação repetida recebe o mesmo substituto; um token gasto apresentado depois disso encerra a sessão, como sinal de roubo.
  • Sem novo ID token. A renovação só devolve id_token se o client ligar ID Token on Refresh.
  • Os tempos de vida são por client. O refresh token vale 60 minutos por padrão.
  • Clients públicos mandam client_id no corpo em vez do cabeçalho Authorization.

Quando session.error valer RefreshTokenError, comece o login de novo: se a sessão da pessoa no Authio ainda estiver viva, ela passa direto.

Proteja rotas

Envolva o auth no proxy para mandar quem não tem sessão para o login antes de a página renderizar. No Next.js 16 o arquivo é proxy.ts; no Next.js 15 e anteriores, é middleware.ts e o export se chama middleware.

proxy.tsTypeScript
import { NextResponse } from 'next/server'
import { auth } from '@/auth'

export const proxy = auth((req) => {
  if (!req.auth) {
    const signInUrl = new URL('/api/auth/signin', req.nextUrl.origin)
    signInUrl.searchParams.set('callbackUrl', req.nextUrl.href)
    return NextResponse.redirect(signInUrl)
  }
})

export const config = {
  matcher: ['/dashboard/:path*', '/settings/:path*'],
}

O proxy decide o que renderiza, não quais dados podem ser lidos: chame auth() de novo nos server components, server actions e route handlers que devolvem dados protegidos, e deixe a sua API validar o access token por conta própria — veja .NET ou validação de tokens.

O authio-provider-nextauth/popup mantém a página onde está e faz o login num popup aberto sobre ela. Dentro dele roda o fluxo normal: o Auth.js troca o código no seu servidor, o cookie de sessão cai na sua origem, e o Authio recebe display=popup e ajusta as telas à janela. Bastam duas páginas pequenas, sem redirect URI extra.

app/auth/popup/page.tsxTypeScript
'use client'

import { useEffect } from 'react'
import { signIn } from 'next-auth/react'
import { startPopupSignIn } from 'authio-provider-nextauth/popup'

export default function PopupStart() {
  useEffect(() => {
    startPopupSignIn(signIn)
  }, [])

  return null
}
  • signInWithPopup nunca rejeita: resolve { ok: true } ou { ok: false, error } com popup_blocked (chame direto do clique), popup_closed, timeout (5 minutos) ou o erro do Auth.js.
  • As páginas são /auth/popup e /auth/popup/done por padrão, na origem do botão; mude com signInWithPopup({ startPath }) e startPopupSignIn(signIn, { donePath }).
  • Um login recusado cai na página de erro do Auth.js: aponte pages.error para uma página que chame completePopupSignIn() e o botão recebe o erro.
  • Não sirva a página com Cross-Origin-Opener-Policy: same-origin; use same-origin-allow-popups.

O pacote não usa response_mode=web_message: o popup volta por /api/auth/callback/authio como qualquer login, e a sua própria página de conclusão avisa o botão. Sem o Auth.js, veja o fluxo em popup no protocolo.

Solução de problemas

O que aparecePor quêO que fazer
Nenhum refresh_token na respostaoffline_access não foi concedido.Libere o escopo no client e acrescente-o à string scope completa.
UntrustedHost em produçãoNEXTAUTH_URL definido no lugar de AUTH_URL no v5.Defina AUTH_URL com o endereço público da aplicação.
Please define a secretAUTH_SECRET está vazio.Gere um com npx auth secret.
Erros de tipo ao importar a entrada raiz numa aplicação v5A entrada v4 importa next-auth/providers/oauth, que não existe no v5.Importe de authio-provider-nextauth/v5.

Próximos passos

Cookies de analytics

Gostaríamos de medir como o console é usado, com o Google Analytics, para enxergar em quais telas as pessoas travam. Isso grava um cookie que reconhece este navegador na sua próxima visita.

Recusar não muda mais nada. Os cookies que mantêm você conectado e lembram o seu idioma e o seu tema são necessários para o console funcionar, e ficam de qualquer forma. Ler a Política de Privacidade