Full-Stack Social Identity: Spring Security 6.x + Next.js 16 + Auth.js
Writing
SECURITY
February 9, 20264 min read

Full-Stack Social Identity: Spring Security 6.x + Next.js 16 + Auth.js

Building a secure "Backend for Frontend" (BFF) architecture using Next.js 16, Auth.js, and Spring Security 6.4 as a stateless OAuth2 Resource Server.

Spring BootNext.jsOAuth2SecurityReact

Identity is hard. Distributed identity? That's a whole other level of pain.

If you're building a modern full-stack application, you likely have a high-performance frontend (Next.js 16) and a robust, transactional backend (Spring Boot 3.4+).

Connecting them securely is where 90% of developers create vulnerabilities. I've seen it all—tokens in local storage, exposed client secrets, you name it.

In this guide, we’re going to fix that. We’ll implement the BFF (Backend for Frontend) pattern. We will use Auth.js (formerly NextAuth) to handle the social login flow, and Spring Security 6.4 to act as a blind, stateless Resource Server that trusts those tokens.

How does the BFF pattern work with Spring Security and Auth.js?

  1. User clicks "Login with GitHub" on Next.js.
  2. Next.js (Server Identity) handles the OAuth2 code grant. It receives an access_token and id_token.
  3. Auth.js stores this session in an encrypted HttpOnly cookie.
  4. Client Request goes to Next.js API Route / Server Action.
  5. Next.js retrieves the access_token from the session and calls Spring Boot.
  6. Spring Boot validates the JWT signature against GitHub's JWKS (JSON Web Key Set).
BFF Pattern Diagram

How do you configure Auth.js with Next.js 16 for social login?

First, let's configure Auth.js. This is our "Public Security" layer. It handles the user-facing complexity so our backend doesn't have to.

// src/auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [GitHub],
  callbacks: {
    async jwt({ token, account }) {
      // Persist the OAuth access_token to the token right after signin
      if (account) {
        token.accessToken = account.access_token
      }
      return token
    },
    async session({ session, token }) {
      // Send properties to the client
      session.accessToken = token.accessToken as string
      return session
    },
  },
})

Crucially, implementing a middleware allows us to attach this token to requests headed for Spring.

// src/lib/api-client.ts
import { auth } from "@/auth";
 
export async function fetchFromSpring(endpoint: string) {
    const session = await auth();
    const token = session?.accessToken;
 
    if (!token) throw new Error("Unauthorized");
 
    const response = await fetch(\`https://api.myapp.com\${endpoint}\`, {
        headers: {
            'Authorization': \`Bearer \${token}\`,
            'Content-Type': 'application/json'
        }
    });
    
    return response.json();
}

How does Spring Security validate OAuth2 tokens from Auth.js?

On the Java side, we don't care about login forms, redirects, or client secrets. We only care about The Token.

Spring Security 6 makes this trivial with oauth2ResourceServer.

@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
 
    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    String issuerUri;
 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(JwtDecoders.fromIssuerLocation(issuerUri))
                )
            );
            
        return http.build();
    }
}

Why is the JWK Set URI critical for token validation?

When you set issuer-uri in application.yml:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://token.actions.githubusercontent.com

Spring Boot automatically:

  1. Calls /.well-known/openid-configuration.
  2. Finds the jwks_uri.
  3. Downloads the public keys used to sign the tokens.
  4. Rotates them automatically if the provider changes keys.

In 2025, browser privacy rules are strict. If your Next.js app is on app.com and Spring is on api.com, you might face cookie issues if you try to share cookies directly.

This is why the Token Exchange approach (Bearer Token) is superior for this stack. The cookie stays with Next.js (SameSite=Lax), and only the secure backend channel sees the Bearer token.

How do you relay tokens to downstream microservices?

If Spring needs to call another downstream microservice on behalf of the user (Token Relay), use WebClient.

@Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
            
    return WebClient.builder()
            .apply(oauth2Client.oauth2Configuration())
            .build();
}

Conclusion

By decoupling the Identity Provider (Next.js/Auth.js) from the Resource Server (Spring Boot), you get the best of both worlds:

  • A user-friendly, social-login capable frontend.
  • A stateless, scalable, and secure backend foundation.

For further reading, see the Spring Security OAuth2 Resource Server Documentation, the Auth.js (NextAuth) Getting Started Guide, and the OpenID Connect Core Specification.

Keep Reading

Frequently Asked Questions

What is the BFF pattern?

Backend for Frontend. It means your Next.js server handles the sensitive detailed OAuth handshake (client secrets, tokens) and communicates securely with your backend APIs, rather than exposing tokens directly to the client browser.

Why use Spring as a Resource Server?

Spring is excellent at enforcing fine-grained authorization logic close to your data. By acting as a Resource Server, it simply validates the JWT access tokens passed by your Next.js BFF.

How do I handle Token Rotation?

Auth.js (NextAuth) handles the refresh token rotation against the provider (e.g., Google/Okta). Spring Security handles the keyset rotation (JWKS) to ensure it can always validate the signatures of incoming tokens.

Last updated: April 2, 2026

Rabinarayan Patra

Rabinarayan Patra

SDE II at Amazon. Previously at ThoughtClan Technologies building systems that processed 700M+ daily transactions. I write about Java, Spring Boot, microservices, and the things I figure out along the way. More about me →

X (Twitter)LinkedIn

Stay in the loop

Get the latest articles on system design, frontend & backend development, and emerging tech trends — straight to your inbox. No spam.