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:
- 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
- Go to Google Cloud Console
- Create or select a project
- Configure OAuth consent screen
- Create OAuth Client ID
- Select Web Application
- Add redirect URI:
http://localhost:8080/login/oauth2/code/google
- Copy Client ID
- 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
openidscope - Use
provider + subjectas 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
OidcUserto access logged-in user details. - Always include
openidscope. - In production, save users using provider plus subject.
- Use HTTPS and protect client secrets.
Next Article
➡️ API Key Authentication for Internal APIs