Full Stack • Java • System Design • Cloud • AI Engineering

OpenID Connect with Spring Security

Learn OpenID Connect (OIDC) with Spring Security step by step. Understand OAuth2 vs OIDC, ID Token, Access Token, UserInfo Endpoint, claims, Google OIDC login, Spring Boot configuration, and real-world implementation.

Introduction

OAuth2 is mainly used for authorization.

OpenID Connect, commonly called OIDC, is built on top of OAuth2 and is used for authentication.

OAuth2 answers:

What can this application access?

OIDC answers:

Who is the logged-in user?

In modern enterprise Java applications, OIDC is commonly used with:

  • Google
  • Okta
  • Auth0
  • Azure AD
  • AWS Cognito
  • Keycloak

In this article, we will learn OpenID Connect with Spring Security step by step.


1. OAuth2 vs OpenID Connect

OAuth2 is about delegated access.

OIDC is about user identity.

Concept OAuth2 OpenID Connect
Main Purpose Authorization Authentication
Token Access Token ID Token + Access Token
User Identity Not guaranteed Standard user identity
Built On OAuth2 OAuth2
Example Allow app to access GitHub repos Login using Google account
flowchart TB

OAuth2["OAuth2"]
--> Access["Access Token"]
--> APIAccess["Access APIs"]

OIDC["OpenID Connect"]
--> IDToken["ID Token"]
--> Identity["User Identity"]

OIDC --> Access2["Access Token"]

2. What Problem Does OIDC Solve?

Without OIDC, every application has to build its own identity system.

That means:

  • User registration
  • Login
  • Password reset
  • MFA
  • Account verification
  • Profile management

OIDC delegates identity to trusted providers.

flowchart LR

User["User"]
--> App["Spring Boot App"]
--> Provider["OIDC Provider"]

Provider --> Identity["Verified User Identity"]
Identity --> App

3. Real-World Example

Imagine an enterprise employee portal.

Employees login using company identity provider.

Employee clicks Login
        ↓
Redirect to Okta / Azure AD / Google
        ↓
User authenticates
        ↓
Spring Boot receives ID Token
        ↓
Application identifies logged-in user
sequenceDiagram

participant Employee
participant SpringBoot
participant OIDCProvider
participant Dashboard

Employee->>SpringBoot: Access /dashboard
SpringBoot->>OIDCProvider: Redirect to login
OIDCProvider->>Employee: Login screen
Employee->>OIDCProvider: Enter credentials
OIDCProvider->>SpringBoot: Authorization code
SpringBoot->>OIDCProvider: Exchange code for tokens
OIDCProvider-->>SpringBoot: ID Token + Access Token
SpringBoot->>Dashboard: Authenticated user

4. Important OIDC Terms

Term Meaning
Identity Provider System that authenticates user
Client Your Spring Boot application
Client ID Public identifier of your app
Client Secret Secret used by backend app
Authorization Code Temporary code returned after login
ID Token Token containing user identity
Access Token Token used to access APIs
Claims User details inside ID Token
Issuer OIDC provider URL
Subject Unique user identifier
mindmap
  root((OIDC))
    Identity Provider
      Google
      Okta
      Azure AD
      Cognito
      Keycloak
    Tokens
      ID Token
      Access Token
      Refresh Token
    Claims
      sub
      email
      name
      picture
    Client
      Spring Boot App

5. ID Token

ID Token is the most important part of OIDC.

It contains user identity information.

Example claims:

{
  "sub": "123456789",
  "email": "[email protected]",
  "email_verified": true,
  "name": "Venu",
  "picture": "https://example.com/profile.png",
  "iss": "https://accounts.google.com",
  "aud": "client-id",
  "exp": 1710000000
}

ID Token Structure

flowchart LR

IDToken["ID Token"]

IDToken --> Header["Header"]
IDToken --> Payload["Claims"]
IDToken --> Signature["Signature"]

6. Access Token vs ID Token

Token Used By Purpose
ID Token Client Application Identify logged-in user
Access Token Resource Server Access protected APIs
Refresh Token Client Application Get new tokens
flowchart TB

Login["OIDC Login"]

Login --> IDToken["ID Token<br/>Who is user?"]
Login --> AccessToken["Access Token<br/>What API access?"]
Login --> RefreshToken["Refresh Token<br/>Get new tokens"]

7. OIDC Authorization Code Flow

This is the most common flow for backend applications.

sequenceDiagram

participant Browser
participant SpringBoot
participant Provider

Browser->>SpringBoot: GET /oauth2/authorization/google
SpringBoot->>Provider: Redirect authorization request
Provider->>Browser: Login page
Browser->>Provider: Login + consent
Provider->>SpringBoot: Redirect with authorization code
SpringBoot->>Provider: Exchange code for tokens
Provider-->>SpringBoot: ID Token + Access Token
SpringBoot-->>Browser: Authenticated session

8. Project Structure

oidc-demo
└── src
    └── main
        └── java
            └── com.codewithvenu.oidc
                ├── config
                │   └── SecurityConfig.java
                ├── controller
                │   └── OidcController.java
                ├── service
                │   └── OidcUserProfileService.java
                └── OidcDemoApplication.java

9. Maven Dependencies

<dependencies>

    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- OAuth2 Client also supports OIDC login -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>

</dependencies>

10. Configure Google OIDC Client

Google Console Steps

  1. Go to Google Cloud Console
  2. Create or select a project
  3. Configure OAuth consent screen
  4. Create OAuth Client ID
  5. Select Web Application
  6. Add redirect URI:
http://localhost:8080/login/oauth2/code/google
  1. Copy Client ID
  2. Copy Client Secret
flowchart TB

A["Google Cloud Console"]
--> B["Create Project"]
--> C["OAuth Consent Screen"]
--> D["Create OAuth Client"]
--> E["Add Redirect URI"]
--> F["Copy Client ID + Client Secret"]

11. application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope:
              - openid
              - profile
              - email

Explanation

openid

Required for OpenID Connect.

profile

Allows access to basic profile details.

email

Allows access to email address.

client-id

Identifies your Spring Boot app.

client-secret

Secret used by backend application.


12. Environment Variables

Mac/Linux:

export GOOGLE_CLIENT_ID="your-google-client-id"
export GOOGLE_CLIENT_SECRET="your-google-client-secret"

Windows PowerShell:

$env:GOOGLE_CLIENT_ID="your-google-client-id"
$env:GOOGLE_CLIENT_SECRET="your-google-client-secret"

Never commit client secrets to GitHub.


13. Spring Security Configuration

package com.codewithvenu.oidc.config;

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

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http)
            throws Exception {

        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .defaultSuccessUrl("/api/oidc/profile", true)
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/")
                .permitAll()
            );

        return http.build();
    }
}

Code Explanation

authorizeHttpRequests

Defines URL security rules.

permitAll

Allows unauthenticated access.

anyRequest().authenticated

Requires login for all other APIs.

oauth2Login

Enables OAuth2/OIDC login.

defaultSuccessUrl

Redirects user after successful login.


14. Create OIDC Controller

package com.codewithvenu.oidc.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/oidc")
public class OidcController {

    @GetMapping("/profile")
    public Map<String, Object> profile(
            @AuthenticationPrincipal OidcUser oidcUser) {

        return Map.of(
                "subject", oidcUser.getSubject(),
                "name", oidcUser.getFullName(),
                "email", oidcUser.getEmail(),
                "emailVerified", oidcUser.getEmailVerified(),
                "issuer", oidcUser.getIssuer().toString(),
                "claims", oidcUser.getClaims()
        );
    }

    @GetMapping("/id-token")
    public Map<String, Object> idToken(
            @AuthenticationPrincipal OidcUser oidcUser) {

        return Map.of(
                "tokenValue", oidcUser.getIdToken().getTokenValue(),
                "issuedAt", oidcUser.getIdToken().getIssuedAt(),
                "expiresAt", oidcUser.getIdToken().getExpiresAt(),
                "claims", oidcUser.getIdToken().getClaims()
        );
    }
}

Code Explanation

@AuthenticationPrincipal

Injects logged-in user details.

OidcUser

Spring Security representation of OIDC authenticated user.

getSubject

Returns unique user ID from provider.

getEmail

Returns email claim.

getIdToken

Returns ID Token object.

getClaims

Returns all claims.


15. Login URL

Spring Security automatically creates this login URL:

http://localhost:8080/oauth2/authorization/google

Start application:

mvn spring-boot:run

Open browser:

http://localhost:8080/oauth2/authorization/google

16. OIDC Login Request Flow in Spring Boot

flowchart TB

User["User"]
--> LoginURL["/oauth2/authorization/google"]

LoginURL --> Provider["Google Login"]

Provider --> Callback["/login/oauth2/code/google"]

Callback --> SpringSecurity["Spring Security"]

SpringSecurity --> OidcUser["OidcUser"]

OidcUser --> Controller["Controller"]

17. Extract Logged-In User Details

In any controller:

@GetMapping("/me")
public String me(@AuthenticationPrincipal OidcUser user) {
    return user.getEmail();
}

In service layer, use SecurityContext:

Authentication authentication =
        SecurityContextHolder.getContext().getAuthentication();

Object principal = authentication.getPrincipal();

if (principal instanceof OidcUser oidcUser) {
    String email = oidcUser.getEmail();
}

18. Save OIDC User to Database

In production, after first login, save user details locally.

Example entity:

@Entity
@Table(name = "app_users")
public class AppUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String provider;

    private String providerSubject;

    private String name;

    private String email;

    private String pictureUrl;
}

Recommended unique key:

provider + providerSubject

Why?

Email can change.

Subject usually remains stable for the provider.

flowchart TB

OidcLogin["OIDC Login"]
--> Claims["Extract Claims"]
--> Subject["Provider Subject"]
--> CheckDB["Find User by Provider + Subject"]
--> SaveUser["Create / Update Local User"]

19. Custom OIDC User Service

Use a custom user service when you need to save/update user after login.

package com.codewithvenu.oidc.service;

import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Service;

@Service
public class CustomOidcUserService extends OidcUserService {

    @Override
    public OidcUser loadUser(OidcUserRequest userRequest) {

        OidcUser oidcUser = super.loadUser(userRequest);

        String provider = userRequest
                .getClientRegistration()
                .getRegistrationId();

        String subject = oidcUser.getSubject();
        String email = oidcUser.getEmail();
        String name = oidcUser.getFullName();

        System.out.println("Provider: " + provider);
        System.out.println("Subject: " + subject);
        System.out.println("Email: " + email);
        System.out.println("Name: " + name);

        // Save or update user in database here

        return oidcUser;
    }
}

Update security config:

.oauth2Login(oauth2 -> oauth2
    .userInfoEndpoint(userInfo -> userInfo
        .oidcUserService(customOidcUserService)
    )
)

20. OIDC with Enterprise Providers

OIDC is commonly used with enterprise identity providers.

flowchart LR

SpringBoot["Spring Boot App"]

SpringBoot --> Google["Google"]
SpringBoot --> Okta["Okta"]
SpringBoot --> AzureAD["Azure AD"]
SpringBoot --> Cognito["AWS Cognito"]
SpringBoot --> Keycloak["Keycloak"]

Typical enterprise flow:

Employee logs in with company SSO
        ↓
OIDC provider validates employee
        ↓
Spring Boot receives ID Token
        ↓
Application assigns internal roles

21. OIDC Claims Mapping

Provider claims may not directly match your application roles.

Example:

{
  "email": "[email protected]",
  "groups": ["engineering", "admin"]
}

You may map:

admin group → ROLE_ADMIN
engineering group → ROLE_DEVELOPER
flowchart LR

Claims["OIDC Claims"]
--> Groups["Provider Groups"]
--> AppRoles["Application Roles"]
--> Authorization["Spring Security Authorization"]

22. Common OIDC Errors

Error Common Cause
redirect_uri_mismatch Redirect URI does not match provider config
invalid_client Wrong client ID or secret
invalid_scope Missing or unsupported scope
login_required User session missing at provider
access_denied User denied consent
nonce mismatch Security validation failed
401 after login Security config issue

Correct Google redirect URI:

http://localhost:8080/login/oauth2/code/google

23. Production Best Practices

  • Use HTTPS
  • Do not hardcode client secrets
  • Store secrets in environment variables or secret manager
  • Always include openid scope
  • Use provider + subject as stable identity key
  • Do not trust email alone as identity
  • Validate issuer and audience
  • Map provider groups to application roles carefully
  • Log login events but never log tokens
  • Use short session timeout
  • Handle logout properly
  • Review provider consent screen

24. OAuth2 Login vs OIDC Login

Feature OAuth2 Login OIDC Login
User identity standard No Yes
ID Token No Yes
Claims Provider specific Standard claims
Best for login Not ideal alone Yes
Spring Principal OAuth2User OidcUser

25. Interview Questions

Q1. What is OpenID Connect?

OpenID Connect is an identity layer built on top of OAuth2.

Q2. What does OIDC provide?

It provides authentication and user identity information.

Q3. What is an ID Token?

ID Token is a token containing user identity claims.

Q4. What is the difference between ID Token and Access Token?

ID Token identifies the user. Access Token authorizes API access.

Q5. What scope is required for OIDC?

openid.

Q6. What is sub claim?

sub is the unique subject identifier of the user from the provider.

Q7. Why not use email as unique identity?

Email can change. Provider subject is more stable.

Q8. Which Spring Security class represents OIDC user?

OidcUser.


26. Key Takeaways

  • OIDC is built on top of OAuth2.
  • OAuth2 is for authorization.
  • OIDC is for authentication.
  • OIDC provides ID Token.
  • ID Token contains user identity claims.
  • Spring Security supports OIDC through OAuth2 Client.
  • Use OidcUser to access logged-in user details.
  • Always include openid scope.
  • In production, save users using provider plus subject.
  • Use HTTPS and protect client secrets.

Next Article

➡️ API Key Authentication for Internal APIs