Integrando o NextAuth.js com o Spring Authorization Server
Configure uma aplicação Next.js para autenticar e autorizar usuários com um servidor Spring de OAuth 2
17/01/2025

Este é um post rápido para demonstrar como adicionar autenticação de usuário ao seu aplicativo web usando OAuth 2 com NextAuth.js e um servidor de autorização Spring. Como há pouca informação disponível online sobre a integração dessas duas tecnologias, vou prover aqui um guia passo a passo. O processo é bastante simples, mas há alguns detalhes para os quais você deve estar atento.
Aplicação de demonstração usando Next.js 15
O código para esta simples aplicação web pode ser encontrado aqui. Ela consiste em uma página inicial contendo apenas um botão de Sign in. O usuário pode então se autenticar com sua conta do Github ou com o nosso servidor de OAuth 2 customizado, do qual iremos tratar na próxima seção.

Após autenticado e autorizado, o usuário é redirecionado à página principal, que agora apresenta uma mensagem de boas-vindas e um link de logout. O menu superior também exibe um link para uma página de profile, onde algumas informações do usuário conectado são exibidas.
A configuração do NextAuth.js reside no arquivo src/auth/auth-options.ts
, onde o provider padrão do Github e nosso provider OAuth personalizado são definidos e adicionados ao objeto authOptions
exportado. O provider personalizado deve ser configurado para utilizar a versão 2 do OAuth e os endpoints expostos pelo nosso servidor de autorização. O escopo deve ser definido como openid
para que o aplicativo cliente tenha acesso às informações básicas de perfil do usuário autenticado.
import GithubProvider from "next-auth/providers/github";
const githubProvider = GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
});
const springOAuthProvider = {
id: "spring",
name: "Spring Auth Server",
type: "oauth",
version: "2",
authorization: "http://localhost:9000/oauth2/authorize",
token: "http://localhost:9000/oauth2/token",
userinfo: "http://localhost:9000/userinfo",
clientId: process.env.SPRING_CLIENT_ID,
clientSecret: process.env.SPRING_CLIENT_SECRET,
clientAuthMethod: "client_secret_basic",
redirectUri: "http://localhost:3000/api/auth/callback",
scope: "openid",
idToken: true,
issuer: "http://localhost:9000",
// jwks_endpoint: 'http://localhost:9000/oauth2/jwks',
wellKnown: "http://localhost:9000/.well-known/openid-configuration",
profile: (profile: any) => {
console.log("profile", profile);
return {
id: profile.user.id.toString(),
name: profile.user.username,
email: profile.user.email,
};
},
};
export const authOptions = {
providers: [githubProvider, springOAuthProvider],
};
Muito embora o endpoint /.well-known/openid-configuration
seja padrão em muitas soluções de autenticação, aqui tivemos que explicitá-lo na configuração do provider para que não houvesse erros durante o processo de autorização do usuário.
As entradas clientId
e clientSecret
serão carregadas das variáveis de ambiente SPRING_CLIENT_ID
e SPRING_CLIENT_SECRET
, respectivamente. Leia o arquivo README.md do projeto para verificar como configurá-las corretamente.
Configurando o NextAuth.js
Como estamos usando o Next.js 15 com o app router neste projeto, devemos exportar um objeto NextAuth
como um handler para chamadas de API no arquivo app/api/auth/[...nextauth]/route.ts
, conforme descrito na documentação oficial.
import { authOptions } from "@/auth/auth-options";
import NextAuth from "next-auth";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Finalmente, devemos criar um arquivo middleware.ts
e colocá-lo na raiz da pasta src
para podermos definir as rotas protegidas do nosso aplicativo. No nosso caso, a única página protegida será /profile
.
export { default } from "next-auth/middleware";
// aplica o next-auth apenas para as rotas definidas
export const config = { matcher: ["/profile"] };
Spring Authorization Server
O código para o nosso servidor de autorização personalizado pode ser encontrado aqui. É basicamente um dos aplicativos de exemplo da documentação oficial do Spring Authorization Server com algumas modificações e suporte para OpenId Connect.
Primeiramente, vamos configurar a porta do servidor e os parâmetros do cliente no arquivo application.yml
, supondo que o aplicativo cliente será executado em http://localhost:3000
.
server:
port: 9000
logging:
level:
org.springframework.security: trace
app:
auth:
client:
id: oidc-client
secret: secret
redirect-uri: "http://localhost:3000/api/auth/callback/spring"
logout-redirect-uri: "http://localhost:3000/"
Observe o parâmetro redirect-uri
. O token final é usado para identificar o provedor OAuth no NextAuth.js, portanto, deve ser igual ao id
do provedor personalizado springOAuthProvider
definido na seção anterior. O id e o client secret também devem corresponder exatamente àqueles definidos no arquivo de configuração do NextAuth.js. Esses parâmetros são usados no método do bean registeredClientRepository
em SecurityConfig
, como mostrado abaixo:
// SecurityConfig.java
...
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// This is just a properties bean that holds the client parameters
private final ClientConfig clientConfig;
...
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient oidcClient = RegisteredClient.withId(randomUUID().toString())
.clientId(clientConfig.id())
.clientSecret("{noop}" + clientConfig.secret())
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri(clientConfig.redirectUri())
.postLogoutRedirectUri(clientConfig.logoutRedirectUri())
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.build();
return new InMemoryRegisteredClientRepository(oidcClient);
}
...
}
Para este aplicativo de demonstração, estamos definindo manualmente um único usuário com nome de usuário user
e senha password
, assim como no exemplo da documentação oficial. Para adicionar informações de perfil do usuário autenticado ao token JWT, também devemos implementar um método construção do bean OAuth2TokenCustomizer
:
// SecurityConfig.java
...
@Configuration
@EnableWebSecurity
public class SecurityConfig {
...
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer(OidcUserInfoService userInfoService) {
return (context) -> {
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
OidcUserInfo userInfo = userInfoService.loadUser(context.getPrincipal().getName());
context.getClaims().claims(claims -> claims.putAll(userInfo.getClaims()));
}
};
}
...
}
Para recuperar as informações do usuário a serem incorporadas no token, definimos uma classe chamada OidcUserInfoService
. Essa classe fornece o método loadUser
, que recebe um nome de usuário e retorna um objeto OidcUserInfo
contendo os dados de perfil. As informações do usuário são recuperadas usando um repositório incorporado na classe de service.
O UserInfoRepository
serve como uma fonte de dados simples, fornecendo as informações de perfil do usuário em um Map. O repositório é inicializado com o único usuário identificado por user
. O método findByUsername
retorna os dados de perfil que correspondem ao nome de usuário fornecido. Aqui está o código que implementa esse processo:
@Service
public class OidcUserInfoService {
private final UserInfoRepository userInfoRepository = new UserInfoRepository();
public OidcUserInfo loadUser(String username) {
return new OidcUserInfo(this.userInfoRepository.findByUsername(username));
}
static class UserInfoRepository {
private final Map<String, Map<String, Object>> userInfo = new HashMap<>();
public UserInfoRepository() {
this.userInfo.put("user", createUser());
}
public Map<String, Object> findByUsername(String username) {
return this.userInfo.get(username);
}
private static Map<String, Object> createUser() {
return Map.of(
"user", Map.of(
"id", 1,
"username", "user",
"name", "User",
"email", "user@email.com"
)
);
}
}
}
Executando a stack
Os processos de execução de cada aplicativo são descritos nos arquivos README localizados em seus respectivos repositórios. Uma vez que ambos os aplicativos (servidor de autenticação Spring e aplicativo Next.js) estejam executando, você pode acessar a interface web navegando para http://localhost:3000
no seu navegador.
Clique no botão Sign in e, em seguida, "Sign in with Spring Auth Server". O navegador deve exibir a página de login do servidor de autenticação. Você pode verificar a guia de rede das ferramentas de desenvolvimento do navegador para verificar que o endpoint authorize
foi chamado.

Após inserir as credenciais de usuário user
e password
, você deve ser redirecionado à página principal. Agora a barra de navegação superior deve exibir um link para a página de perfil e um botão para sair. Ao clicar no link profile, o aplicativo deve exibir algumas informações sobre o usuário conectado, como mostrado na figura abaixo:

O link Logout deve redirecioná-lo à página de sign-out padrão do NextAuth.js, solicitando que você confirme se realmente deseja encerrar sua sessão. Após confirmar, você será redirecionado para a página inicial com a sessão finalizada.
E é isso...
Neste artigo desenvolvemos um setup de autenticação funcionando com um servidor de autorização Spring e um aplicativo Next.js usando NextAuth.js. Isso permite que os usuários façam login de forma segura e acessem páginas protegidas dentro da sua aplicação. Você pode usar esse projeto como ponto de partida para adicionar mais recursos ou personalizar o fluxo de autenticação conforme necessário.