OAuth 2.0 / OIDC for Spring Boot microservices using PingFederate

1) Big picture

[Browser / Mobile / SPA]
      |  (Auth Code + PKCE)
      v
+------------------------+          (JWT or opaque access tokens)
|   PingFederate (AS)    |  --->  [JWKS endpoint / Introspection]
|  /as/authorization...  |         /pf/JWKS      /as/introspect.oauth2
|  /as/token.oauth2      |
|  /.well-known/openid...|
+-----------+------------+
            |
            |  (Bearer token on API calls)
            v
      [API Gateway]*  ← optional central policy & caching
            |
     +------+------+
     |  Spring Boot|  (Resource Servers)
     |  microservices
     +------+------+
            |
         [Data/Domain]

Why this shape?

  • For user-facing apps, use Authorization Code + PKCE; PingFederate publishes discovery metadata and supports S256 PKCE. (Ping Identity Docs)
  • For service-to-service, use Client Credentials.
  • For high TPS, prefer JWT access tokens and local validation via JWKS (no per-request network hop); fall back to opaque tokens + introspection only when you need immediate revocation or rich token lookups. PingFederate exposes well-known OIDC metadata (issuer, JWKS), authorization and token endpoints, and an OAuth Introspection endpoint. (Ping Identity Docs)

2) PingFederate endpoints you’ll use

  • Authorization: https://<pf-host>:9031/as/authorization.oauth2
  • Token: https://<pf-host>:9031/as/token.oauth2
  • Discovery (OIDC): https://<pf-host>:9031/.well-known/openid-configuration (contains issuer, jwks_uri, introspection_endpoint, etc.; example shows jwks_uri: https://<pf-host>:9031/pf/JWKS and introspection_endpoint: /as/introspect.oauth2). (Ping Identity Docs)

3) Token strategy (choose one per environment)

A. Signed JWT access tokens (recommended for scale)

  • Spring services validate tokens offline using jwks_uri from discovery. This avoids a call to PF on every request and scales linearly with CPU. Spring Security auto-configures this from issuer-uri. (Home)

B. Opaque tokens + introspection (revocation-friendly)

  • Services call PingFederate /as/introspect.oauth2 (POST, application/x-www-form-urlencoded) with client auth to check active, scope, exp, etc. Higher latency; cache results at gateway if needed. (Ping Identity Docs)


4) Spring Boot configs

4.1 Resource Server — JWT (zero PF hop per request)

# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://<pf-host>:9031   # Spring discovers jwks_uri automatically

Spring will resolve discovery → JWKS and verify signatures locally. (Home)

HTTP security & scope→role mapping

@Configuration
@EnableMethodSecurity
class SecurityConfig {

  @Bean
  SecurityFilterChain api(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(auth -> auth
        .requestMatchers("/actuator/**").permitAll()
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
      )
      .oauth2ResourceServer(oauth -> oauth
        .jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthorities()))
      );
    return http.build();
  }

  private Converter<Jwt, ? extends AbstractAuthenticationToken> grantedAuthorities() {
    var delegate = new JwtGrantedAuthoritiesConverter();
    delegate.setAuthorityPrefix("ROLE_");
    delegate.setAuthoritiesClaimName("scope"); // or "scp" / custom claim mapped in PF
    return new JwtAuthenticationConverter() {{
      setJwtGrantedAuthoritiesConverter(delegate);
    }};
  }
}

Map whatever PF places scopes/roles into (e.g., scope, groups, custom claims via attribute mapping on the PF side).

4.2 Resource Server — Opaque token introspection

spring:
  security:
    oauth2:
      resourceserver:
        opaque-token:
          introspection-uri: https://<pf-host>:9031/as/introspect.oauth2
          client-id: api-resource
          client-secret: ${INTROSPECTION_SECRET}

PingFederate’s standard introspection endpoint returns active, scope, and other attributes; beware the added network cost. (Ping Identity Docs)


5) Browser / SPA flow (Auth Code + PKCE)

  1. App fetches discovery, redirects user to PF authorization endpoint with code_challenge=S256.
  2. Backend exchanges code at token endpoint for access_token (+ id_token if OIDC).
  3. App calls your APIs with the access token; services validate JWT locally or introspect if opaque.
    PingFederate’s discovery shows PKCE methods and all core URLs needed. (Ping Identity Docs)

Security tips

  • Always use PKCE (S256) for public clients.
  • Keep access tokens short-lived (e.g., 5–10 min) and rotate refresh tokens. (PF supports refresh tokens via standard token endpoint.) (Ping Identity Docs)

6) Service-to-service flow (Client Credentials)

  • Register a PF client with client_credentials.
  • Caller obtains token from /as/token.oauth2 and calls your APIs.
  • Resource servers validate JWT via JWKS or introspect opaque tokens. (Ping Identity Docs)

7) Gateway & high-TPS hardening (optional but useful)

  • Central validation: If you run an API gateway, do JWT verification once at the edge; pass a stripped verified identity context to services.
  • Introspection caching: If you must use opaque tokens, gateway can cache active responses briefly (e.g., TTL = remaining exp or a few seconds) to cut PF round-trips.
  • Clock skew: Allow small skew (±60s) in Spring validation to avoid intermittent 401s during key rotation.
  • Key rotation: PF exposes jwks_uri—Spring will re-fetch as needed; ensure egress to PF for JWKS. (Ping Identity Docs)

8) Claims & authorization design

  • Scopes: Issue business scopes from PF (e.g., orders.read, orders.write) and gate APIs with @PreAuthorize("hasAuthority('SCOPE_orders.read')").
  • Roles/Groups: Map directory groups → custom claim (e.g., roles) in PF, then convert to ROLE_* in Spring (converter above).
  • Audiences: Use PF Access Token Manager audience/resource settings so tokens are audience-scoped to your APIs. (Ping Identity Docs)

9) Observability & ops

  • Metrics: 401/403 rates, token validation latency, cache hit rate (if introspecting), PF endpoint p50/p95.
  • Tracing: Propagate traceId through gateway and services.
  • HA PF: Run PF behind LB with sticky session only if you use PF session pages; OIDC flows themselves are stateless.
  • Discovery health: Periodically check /.well-known/openid-configuration and JWKS reachability. (Ping Identity Docs)

10) Quick “copy-paste” checklist

  • Decide JWT vs Opaque; prefer JWT + JWKS for high TPS. (Home)
  • Configure Spring Boot Resource Server with issuer-uri (JWT) or introspection-uri (opaque). (Home, Ping Identity Docs)
  • For browser apps, use Auth Code + PKCE (S256). (Ping Identity Docs)
  • Know your PF endpoints: /as/authorization.oauth2, /as/token.oauth2, /.well-known/openid-configuration, /pf/JWKS, /as/introspect.oauth2. (Ping Identity Docs)
  • Map scopes/roles in PF attribute mapping → Spring GrantedAuthorities.
  • Set short token TTLs; rely on refresh tokens where needed (back-channel only). (Ping Identity Docs)

If you want, I can tailor this to your exact topology (gateway/no gateway, JWT vs opaque), and hand you ready-to-run Spring Boot starter modules (JWT and Introspection variants) with minimal code.

Back to blog

Leave a comment

Please note, comments need to be approved before they are published.