Java

Sign in with NIA — Spring Boot

Add “Sign in with NIA” to a Spring Boot app with Spring Security OAuth2 Client and the standard OpenID Connect flow. Follow the steps top to bottom — tick each one off as you go.

~15 minMediumSpring Security OAuth2 Client
Try OAuth Flow
Integration progress
0/16

01Overview

This guide adds “Sign in with NIA” to a Spring Boot application using the standard OpenID Connect flow. NIA is a conformant OIDC provider, so you use Spring Security OAuth2 Client — Spring's built-in OAuth/OIDC client — with no NIA-specific SDK.

Spring Boot runs as a confidential client: the browser is redirected to NIA to sign in, and your Spring server exchanges the returned code for tokens using your client secret. Spring Security handles PKCE, state, and ID-token signature validation for you.

  • Flow: Authorization Code + PKCE (S256)
  • Tokens: RS256 access & ID tokens, opaque refresh token
  • You write: a security config and (optionally) a controller that reads the principal

02Requirements

  • Java 17+ and Spring Boot 3+
  • A NIA account with access to the client dashboard
  • A registered OAuth client (client_id + client_secret)
  • Your app's redirect URI registered on that client
  • Maven or Gradle build tooling

03Installation

Add the spring-boot-starter-oauth2-client dependency. It pulls in Spring Security and the OAuth2/OIDC client support used throughout this guide.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

04Required OAuth library

Register NIA once in application.yml. The issuer-uri points at NIA's discovery document — every endpoint, the JWKS URL, and supported algorithms are configured automatically from it, and PKCE is enabled automatically for this registration.

application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          nia:
            client-id: ${NIA_CLIENT_ID}
            client-secret: ${NIA_CLIENT_SECRET}
            scope: openid,email,profile
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/nia"
        provider:
          nia:
            issuer-uri: https://dev-api.niaauth.com
Add offline_access to scope only if you need refresh tokens (see step 14).

05Environment Variables

Never hard-code secrets. The issuer already lives in application.yml — only the client credentials need to come from the environment.

.env
NIA_CLIENT_ID=your-client-id
NIA_CLIENT_SECRET=your-client-secret
With Spring Boot, export these as real environment variables (or use spring-dotenv / your platform's secret manager) — Spring reads ${NIA_CLIENT_ID} placeholders from the process environment at startup.

06Register OAuth Client

In the NIA dashboard, create the client your Spring Boot app will authenticate as:

  • Open Dashboard → Clients → New client
  • Client type: Web (confidential)
  • Allowed scopes: openid email profile (add offline_access for refresh)
  • Add your redirect URI (next step)
  • Copy the client_id and client_secret — the secret is shown once

07Redirect URI

NIA matches redirect URIs exactly — scheme, host, port, and path must be identical, with no trailing-slash difference. Spring's default redirect template is {baseUrl}/login/oauth2/code/{registrationId}, which resolves to /login/oauth2/code/nia for this registration. Register one URI per environment.

redirect URIs
# Development
http://localhost:8080/login/oauth2/code/nia

# Production
https://app.example.com/login/oauth2/code/nia
A mismatch is the #1 integration error. The path must be exactly /login/oauth2/code/nia — Spring will not process the callback on any other path for this registration.

08Issuer

The issuer is the single URL every OIDC library needs. Its discovery document advertises all endpoints below.

issuer & endpoints
Issuer:        https://dev-api.niaauth.com
Discovery:     https://dev-api.niaauth.com/.well-known/openid-configuration
Authorization: https://dev-api.niaauth.com/oauth/authorize
Token:         https://dev-api.niaauth.com/oauth/token
UserInfo:      https://dev-api.niaauth.com/oauth/userinfo
JWKS:          https://dev-api.niaauth.com/oauth/jwks.json

09Authorization Flow

Add a security config that requires authentication and enables OAuth2 login. Spring redirects unauthenticated users to NIA automatically — you do not write a login view.

SecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
            .oauth2Login(Customizer.withDefaults());
        return http.build();
    }
}

10Token Exchange

Spring Security performs the authorization code exchange — including sending the PKCE verifier — at /login/oauth2/code/nia automatically. You don't write this step; it happens inside the filter chain configured above.

The resulting tokens are stored in an OAuth2AuthorizedClient that you can retrieve later (see Session Management and Refresh Tokens).

11JWKS Validation

NIA signs ID tokens with RS256. Spring Security fetches the public keys from the JWKS endpoint (discovered automatically from issuer-uri), caches them, and verifies the signature, iss, aud, exp, and nonce during login. You do not verify manually.

To confirm what was validated, inspect the parsed claims from the principal:

verify (illustrative)
@GetMapping("/whoami")
public Map<String, Object> whoami(@AuthenticationPrincipal OidcUser principal) {
    Map<String, Object> claims = principal.getClaims();
    // signature (RS256, via JWKS), iss, aud, exp and nonce are already
    // verified by Spring Security before this controller method runs.
    return claims;
}
Keys rotate. Because Spring refetches JWKS by kid, rotation needs no code change on your side.

12Session Management

Once login succeeds, Spring stores the authenticated user in the HttpSession. Read it with @AuthenticationPrincipal OidcUser in any controller:

ProfileController.java
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProfileController {

    @GetMapping("/api/me")
    public Map<String, Object> me(@AuthenticationPrincipal OidcUser principal) {
        return Map.of(
            "sub", principal.getSubject(),
            "email", principal.getEmail(),
            "name", principal.getFullName(),
            "claims", principal.getClaims()
        );
    }
}
Requests that hit a protected endpoint without a session are redirected to NIA by the filter chain configured in Authorization Flow — no manual guard needed.

13Refresh Tokens

If you requested offline_access, NIA returns a refresh token, which Spring Security stores alongside the access token in the OAuth2AuthorizedClient. Use an OAuth2AuthorizedClientManager (backed by a refresh-token provider) so tokens are refreshed automatically when you call NIA-protected resources with WebClient.

WebClientConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.*;
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clients,
            OAuth2AuthorizedClientRepository authorizedClients) {

        OAuth2AuthorizedClientProvider provider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                .authorizationCode()
                .refreshToken() // auto-refreshes using the stored refresh token
                .build();

        DefaultOAuth2AuthorizedClientManager manager =
            new DefaultOAuth2AuthorizedClientManager(clients, authorizedClients);
        manager.setAuthorizedClientProvider(provider);
        return manager;
    }

    @Bean
    WebClient niaWebClient(OAuth2AuthorizedClientManager manager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(manager);
        oauth2.setDefaultClientRegistrationId("nia");
        return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
    }
}
NIA rotates refresh tokens on use — Spring Security persists the replacement for you via the OAuth2AuthorizedClientRepository.

14Logout

Clear the local session and send the user to NIA's end-session endpoint so they are signed out of NIA too, using OidcClientInitiatedLogoutSuccessHandler. Register your post_logout_redirect_uri on the client.

SecurityConfig.java
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;

@Bean
SecurityFilterChain filterChain(HttpSecurity http, ClientRegistrationRepository clients) throws Exception {
    OidcClientInitiatedLogoutSuccessHandler logoutHandler =
        new OidcClientInitiatedLogoutSuccessHandler(clients);
    logoutHandler.setPostLogoutRedirectUri("{baseUrl}/");

    http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
        .oauth2Login(Customizer.withDefaults())
        .logout(l -> l.logoutSuccessHandler(logoutHandler));

    return http.build();
}

15Deployment

Before going live:

  • Register the production redirect URI (HTTPS) on the client
  • Set NIA_CLIENT_ID / NIA_CLIENT_SECRET as real env vars on your host — never commit them
  • Serve over HTTPS end-to-end so {baseUrl} resolves to https://
  • If behind a reverse proxy or load balancer, set server.forward-headers-strategy so Spring builds the correct redirect URI
  • Use a shared/persistent session store (e.g. Redis) behind multiple instances
application.yml (production)
server:
  forward-headers-strategy: framework

spring:
  security:
    oauth2:
      client:
        registration:
          nia:
            redirect-uri: "{baseUrl}/login/oauth2/code/nia"

16Troubleshooting

  • redirect_uri_mismatch — the path must be exactly /login/oauth2/code/nia (scheme/host/port must match the registered URI too).
  • invalid_client — wrong NIA_CLIENT_SECRET, or the client's token-auth method differs from what Spring sends.
  • issuer mismatch / discovery failure — issuer-uri must be exactly https://dev-api.niaauth.com, with no trailing slash.
  • redirect URI built as http:// behind a proxy — set server.forward-headers-strategy: framework (or native) so {baseUrl} reflects X-Forwarded-* headers.
  • invalid_scope — the scope isn't in the client's allowed_scopes; add it in the dashboard.
  • ID-token signature — usually server clock skew; sync time via NTP.