servicosgovbr / manual-roteiro-integracao-login-unico

Roteiro de Integração do Login Único para os órgãos
https://manual-roteiro-integracao-login-unico.servicos.gov.br
42 stars 16 forks source link

[Pergunta] - Alguém já conseguiu ou tentou integrar o gov.br usando next-auth? #36

Closed lgcavalheiro closed 7 months ago

lgcavalheiro commented 7 months ago

Boa tade pessoal, queria saber se alguém já conseguiu integrar o gov.br em um frontend next usando o next-auth, estou tendo problemas em integrar, o next-auth não faz mais nenhuma requisição depois da GET de authorization. A princípio ele deveria fazer tudo sozinho por causa da url de .well-known, contando que o gov.br seja compliant com o Openid Connect.

Alguns arquivos relevantes que tenho com relação as configs, caso seja útil:

// api/auth/[...nextauth].ts
import NextAuth, { type NextAuthOptions } from 'next-auth';
import { createHash } from 'node:crypto';

export const authOptions: NextAuthOptions = {
  debug: true,
  providers: [
    {
      id: 'gov.br',
      name: 'Gov.br',
      type: 'oauth',
      wellKnown: process.env.WELL_KNOWN,
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      authorization: {
        params: {
          scope: 'openid+email+profile',
          nonce: '<REDACTED>',
          code_challenge_method: 'S256',
          code_challenge: createHash('SHA256')
            .update(process.env.VERIFIER!)
            .digest('base64url'),
          redirect_uri:
            '<REDACTED_ENDPOINT>',
          response_type: 'code',
        },
      },
      profile(profile) {
        return {
          id: profile.<REDACTED>,
          ...<REDACTED>
        };
      },
    },
  ],
  secret: process.env.NEXTAUTH_SECRET,
  session: {
    strategy: 'jwt',
    maxAge: 60 * 60,
  },
// none of the following callbacks are being triggered:
  callbacks: {
    async signIn(params) {
      console.log('SIGN IN CALLBACK: ', params);
      const isAllowedToSignIn = true;
      if (isAllowedToSignIn) {
        return true;
      } else {
        return false;
      }
    },
    async jwt({ token, account }: any) {
      console.log('JWT CALLBACK: ', token, account);
      return token;
    },
    async redirect({ url, baseUrl }) {
      console.log('REDIRECT CALLBACK: ', url, baseUrl);
      return baseUrl;
    },
    async session({ session, user, token }) {
      console.log('SESSION CALLBACK: ', session, user, token);
      return session;
    },
  },
  pages: {
    signIn: '<REDACTED_ROUTE>',
  },
};

export default NextAuth(authOptions);
// api/session.ts
import type { NextApiRequest, NextApiResponse } from "next";

export default async function session(
  req: NextApiRequest,
  res: NextApiResponse
) {
  console.log("HIT SESSION: ");

  const token = await getToken({ req });

  res.end(
    JSON.stringify({
      token,
    })
  );
}
// src/middleware.ts
import { getToken } from "next-auth/jwt";
import { withAuth } from "next-auth/middleware";

export default withAuth(
  function middleware(req) {
    console.log("NEXTAUTH SESSION: ", req.nextauth);
  },
  {
    callbacks: {
      authorized: async ({ req, token }) => {
        console.log("TOKEN AUTH CALLBACK: ", token, req.cookies);

        return Boolean(token?.<REDACTED>);
      },
    },
    pages: {
      signIn: "<REDACTED_ROUTE>",
    },
  }
);

export const config = { matcher: ["<REDACTED>"] };
weltonrodrigo commented 7 months ago

Amigo, se você me permite a sugestão, não reutilize valores de nonce e jamais do code_challenge do PCKE. Ele é um ponto chave na segurança do processo de login.

No seu código, parece que você está usando valores fixos pra esses dois parâmetros. Isso não é tão grave porque seu código roda no backend, mas mesmo assim é um risco desnecessário

Eu suponho que a lib next-auth já lide com isso pra você se deixar esses parâmetros em branco.

Coisas pra você checar:

Baseado nisso aqui: https://next-auth.js.org/configuration/providers/oauth#using-a-custom-provider eu suponho que o config seja:

{
  id: "gov.br",
  name: "Gov.br",
  type: "oauth",
  wellKnown: "https://sso.acesso.gov.br/.well-known/openid-configuration",
  authorization: { params: { scope: "openid email profile phone govbr_confiabilidades" } }, // adicionado scopes necessários
  idToken: true, 
  checks: ["pkce", "state"],  // usando PCKE e supondo que a lib vai usar nonce no state
  profile(profile) {
    return {
      id: profile.sub, 
      cpf: profile.sub, // no gov.br o cpf é o subject
      name: profile.name,
      email: profile.email
      // image: profile.picture, // pra conseguir a imagem é necessário uma outra chamada.
    }
  },
}

Até onde eu entendi pela documentação, o token que vai ser recebido pelo frontend não é o token gov.br, mas um token emitido pelo next-auth depois que ele verificar no gov.br que o login foi bem sucedido, de modos que os callbacks que você comentou ali:

// none of the following callbacks are being triggered:
  callbacks: {
// …
}

Não vão ser chamados durante o fluxo de login no gov.br, mas possivelmente depois que ele foi completado com sucesso.

lgcavalheiro commented 7 months ago

Muito obrigado pelas dicas @weltonrodrigo ! Vendo melhor essa questão do next-auth, decidi que é melhor deixar ele de lado em prol de uma outra solução que não seja tão "caixa preta" igual ele (iron-session (gerenciamento de sessões no front) + jose (validação usando JWK) , implementando o fluxo manualmente msm). Sendo assim vou fechar a issue então.

weltonrodrigo commented 7 months ago

Muito obrigado pelas dicas @weltonrodrigo ! Vendo melhor essa questão do next-auth, decidi que é melhor deixar ele de lado em prol de uma outra solução que não seja tão "caixa preta" igual ele (iron-session (gerenciamento de sessões no front) + jose (validação usando JWK) , implementando o fluxo manualmente msm). Sendo assim vou fechar a issue então.

Cara, se aceita a sugestão, eu sugiro fortemente que não implemente o oauth manualmente. O protocolo é cheio de pequenos detalhes que parecem simples mas escondem um problemão por trás.

Da uma olhada no OAuth 2.0 Threat Model and Security Considerations que tu vai entender melhor o desafio que é implementar corretamente.