skip to content

How do you get an access token inside a controller using @RegisteredOAuth2AuthorizedClient, and what happens under the hood?

level: middleimportance: must knowfreq 55%

answer

  1. @RegisteredOAuth2AuthorizedClient("id") OAuth2AuthorizedClient
  2. OAuth2AuthorizedClientArgumentResolver -> Manager
  3. Manager -> Providers -> save to Repository
  4. clockSkew 60s proactive refresh
  5. auth_code with no client -> redirect (ClientAuthorizationRequiredException)

basics

~10 s

Add an OAuth2AuthorizedClient parameter annotated @RegisteredOAuth2AuthorizedClient("registration-id") to your controller method. Spring resolves it, authorizing (or refreshing) as needed, and you read client.getAccessToken().getTokenValue().

solid answer

~40 s

You declare a controller method parameter `@RegisteredOAuth2AuthorizedClient("my-api") OAuth2AuthorizedClient client`. An argument resolver — `OAuth2AuthorizedClientArgumentResolver` — intercepts it and calls the configured `OAuth2AuthorizedClientManager`. The manager looks up an existing authorized client for that registrationId + current principal via the `OAuth2AuthorizedClientRepository`; if none exists (or the token is expired and refreshable) it runs the appropriate `OAuth2AuthorizedClientProvider` to authorize/re-authorize, stores the result, and hands you the `OAuth2AuthorizedClient`. You then use `client.getAccessToken().getTokenValue()` for the downstream `Authorization: Bearer` header. Note: for authorization_code, if the user has not yet consented, resolution can trigger a redirect to the provider rather than returning a client synchronously — so the annotation is most seamless for client_credentials or already-authorized users.

code

java · 17 lines
java
@RestController
class ApiController {

    private final RestClient rest = RestClient.create();

    @GetMapping("/orders")
    List<Order> orders(
            @RegisteredOAuth2AuthorizedClient("my-api") OAuth2AuthorizedClient client) {

        String token = client.getAccessToken().getTokenValue();
        return rest.get()
            .uri("https://api.example.com/orders")
            .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
            .retrieve()
            .body(new ParameterizedTypeReference<>() {});
    }
}

go deeper

for a junior

Know the annotation exists and yields an OAuth2AuthorizedClient whose token you send downstream.

for a middle

Explain the resolver→manager→provider→repository chain and the refresh behaviour.

for a senior

Contrast synchronous return for client_credentials vs redirect for authorization_code; mention WebClient/RestClient auto-attach alternatives.

for a principal

Reason about clockSkew tuning, thread-context requirements of the servlet manager, and when to bypass the annotation for a service-layer manager.

## The annotation **`@RegisteredOAuth2AuthorizedClient`** is a Spring MVC/WebFlux method-parameter annotation. You put it on a parameter of type **`OAuth2AuthorizedClient`** and give it the **registrationId** of the client you want: ```java @GetMapping("/data") String data(@RegisteredOAuth2AuthorizedClient("my-api") OAuth2AuthorizedClient client) { … } ``` Omitting the id makes Spring infer it from the current `OAuth2AuthenticationToken` (only works when the user logged in via that same provider). ## The resolution chain At request time **`OAuth2AuthorizedClientArgumentResolver`** handles the parameter. It builds an `OAuth2AuthorizeRequest` (registrationId + the current `Authentication` principal + the `HttpServletRequest`) and passes it to the **`OAuth2AuthorizedClientManager`** (default `DefaultOAuth2AuthorizedClientManager`). The manager: 1. Loads any existing `OAuth2AuthorizedClient` from the **`OAuth2AuthorizedClientRepository`** for (registrationId, principalName). 2. Delegates to a chain of **`OAuth2AuthorizedClientProvider`s**. Each provider decides whether it should act: `client_credentials`/`authorization_code` provide an *initial* token; `refresh_token` re-authorizes an *existing but expired* one. 3. If a provider returns a new/updated client, the manager **saves** it back to the repository and returns it. 4. If everything is still valid, it returns the existing client unchanged. ## Token freshness and clock skew Providers judge expiry with a **`clockSkew`** (default 60 seconds) so a token about to expire is refreshed proactively. With a refresh token present, `RefreshTokenOAuth2AuthorizedClientProvider` swaps it for a fresh access token transparently — your controller code never changes. ## The authorization_code caveat For the authorization_code grant, if there is no stored client yet, the flow needs a browser redirect to the provider for user consent. The argument resolver signals this by throwing `ClientAuthorizationRequiredException`, which the `OAuth2AuthorizationRequestRedirectFilter` turns into a redirect. So the annotation returns a client *synchronously* only when one already exists or when the grant (client_credentials, refresh_token) needs no user interaction. ## Alternatives Outside a controller (e.g. a service or scheduled job) you inject the `OAuth2AuthorizedClientManager` (or `OAuth2AuthorizedClientService`) directly and call `authorize(...)` yourself. For HTTP calls, the `ServletOAuth2AuthorizedClientExchangeFilterFunction` (WebClient) or `OAuth2ClientHttpRequestInterceptor` (RestClient) can attach the token automatically instead. ## When to use Use the annotation for the common case: a controller needs a token to call a downstream API for the current request/principal.

  • What happens if the registrationId uses authorization_code and no token is stored yet?
    The argument resolver raises ClientAuthorizationRequiredException; the redirect filter sends the user to the provider for consent. It cannot return a client synchronously without user interaction — unlike client_credentials.
  • How would you get the token in a non-web background job instead?
    Inject an OAuth2AuthorizedClientManager (typically AuthorizedClientServiceOAuth2AuthorizedClientManager) and call manager.authorize(OAuth2AuthorizeRequest…), since there is no HttpServletRequest/request context available.

saying these in an interview costs you the question

  • Thinking the annotation always triggers a browser redirect.
  • Believing the controller method performs the token exchange itself.
  • Assuming it works in a background thread with no request context (the servlet manager needs one).

context