Quickstarts · spring-boot
Spring Boot 3 — Hub login with Spring Security OAuth2
Add OAuth 2.0 / OIDC to a Spring Boot 3 web app via Spring Security 6's OAuth2 client. Five steps, ~10 lines of YAML.
- spring-boot
- java
- kotlin
- oauth2
- oidc
Tested against:framework: Spring Boot 3.3springSecurity: 6.3
Prereqs
- Spring Boot 3.3+ (Java 21 or Kotlin 2.x)
- A Thoryn account
Step 1 — Register a confidential client in Hub
hub clients create \
--name "My Spring app" \
--redirect-uri "http://localhost:8080/login/oauth2/code/thoryn" \
--grant-types authorization_code,refresh_token \
--scopes "openid email profile"Step 2 — Add the dependency
build.gradle.kts:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
}Step 3 — Configure
application.yaml:
spring:
security:
oauth2:
client:
provider:
thoryn:
issuer-uri: https://hub.thoryn.org
registration:
thoryn:
provider: thoryn
client-id: ${THORYN_CLIENT_ID}
client-secret: ${THORYN_CLIENT_SECRET}
scope: openid,email,profile
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"That's it for the framework — Spring Security 6 picks up OIDC discovery from the issuer URI.
Step 4 — Render the user
@RestController
class HomeController {
@GetMapping("/")
fun home(@AuthenticationPrincipal principal: OidcUser?): Map<String, Any?> {
if (principal == null) return mapOf("login" to "/oauth2/authorization/thoryn")
return mapOf(
"name" to principal.fullName,
"email" to principal.email,
"claims" to principal.claims,
)
}
}Step 5 — Run it
THORYN_CLIENT_ID=... THORYN_CLIENT_SECRET=... ./gradlew bootRunhttp://localhost:8080 redirects to /oauth2/authorization/thoryn, lands at Hub, returns to your app with OidcUser populated.
What's next
Troubleshooting
InvalidIssuerException: theissuer-urimust match Hub's issuer claim exactly, including trailing slashes (or absence thereof).- Refresh tokens missing: add
offline_accessto the scope list.