NextAuth - Implementando "Custom Login Pages" com "Credentials Sign in" utilizando App Router
Tags: nextauth, nextjs, approuter
Published: 2023-10-15 17:11:55

Quero abordar um assunto muito específico relacionado a implementação de uma página de login personalizada com Credentials Sign in utilizando [NextAuth](https://next-auth.js.org/) com a estrutura [App Router](https://nextjs.org/docs/app/building-your-application) do Next.js.

Algo que ficou muito claro para mim é que o NextAuth foi construído para funcionar com a estrutura [Pages Router](https://nextjs.org/docs/pages/building-your-application) do Next.js e sua compatibilidade com a App Router definitivamente não é a ideal.

## Entendendo a lógica de funcionamento

Ao optar por utilizar uma página de login personalizada é preciso ficar atendo aos requisitos para tal. Afinal, aqui estamos optando por personalizar a experiência de Login de nossos usuários, o que de fato pode ser algo extremamente positivo, porém, também pode ser um tanto trabalhoso.

Quero focar na implementação do login com [credenciais](https://next-auth.js.org/configuration/pages#credentials-sign-in), onde defino uma lógica personalizada para verificar o usuário.

Vamos pensar no cenário onde necessitamos validar o nome e senha do usuário. A documentação do NextAuth nos orienta a criar um formulário relativamente simples, porém, com uma informação crucial: csrfToken.

```html
<form method="post" action="/api/auth/callback/credentials">
    // Este campo recebe o csrfToken e é obrigatório, porém, é um campo oculto.
    <input name="csrfToken" type="hidden" defaultValue={ csrfToken } />
    <input id="email" name="email" type="email" placeholder="Digite seu Email" required />
    <input id="password" name="password" type="password" placeholder="Digite sua Senha" required />

    <button type="submit">Entrar</button>
</form>
```

## O problema

Aqui reside o maior problema! Como obter o csrfToken? Bom, o NextAuth nos oferece uma função para obtê-lo: `getCsrfToken()`, conforme podemos ver na [documentação](https://next-auth.js.org/getting-started/client#getcsrftoken). Essa função funciona muito bem na Pages Router do Next.js, porém, ela simplesmente não funciona na App Router. Não funciona pois ela retorna um token diferente do realmente definido pela aplicação.

Esse token é armazenado como um cookie em nosso navegador e é muito fácil fazer essa comparação.

## Como obter o CSRF Token da forma correta

O Next.js nos oferece uma função para obter os [cookies](https://nextjs.org/docs/app/api-reference/functions/cookies) do navegador.

```ts
import { cookies } from 'next/headers'

export default async function SignIn() {
    const cookieStore = cookies()

    // Observe que estou utilizando uma variável de ambiente
    const csrfToken = cookieStore.get( `${ process.env.NEXTAUTH_CSRF_TOKEN }` )?.value.split( '|' )[0]

    return '...'
}
```

A explicação do código é relativamente simples, obtenho o cookie informando o seu nome e recupero a parte dele que realmente necessito.

O ponto principal aqui é que em desenvolvimento o nome do cookie é _next-auth.csrf-token_ e em produção ___Host-next-auth.csrf-token_, por esse motivo defini uma variável de ambiente para informar o nome correto do cookie.

Essa é uma implementação simples que resolve o problema.

```ts
import { cookies } from 'next/headers'

export default async function SignIn() {
    const cookieStore = cookies()
    const csrfToken = cookieStore.get( `${ process.env.NEXTAUTH_CSRF_TOKEN }` )?.value.split( '|' )[0]
    
    return (
        <form method="post" action="/api/auth/callback/credentials">
            <input name="csrfToken" type="hidden" defaultValue={ csrfToken } />
            <input id="email" name="email" type="email" placeholder="Digite seu Email" required />
            <input id="password" name="password" type="password" placeholder="Digite sua Senha" required />

            <button type="submit">Entrar</button>
        </form>
    )
}
```