Skip to content

Missing refresh token when using client credentials #116

Open

Description

@sled

The access token doesn't have a refresh token if we use client_credentials as grant_type - however the OAuthAccessTokenProvider tries to refresh the token if it's expired instead of requesting a new one, which results in this error:

oauth2-2.0.9/lib/oauth2/access_token.rb:118:in `refresh': A refresh_token is not available (RuntimeError)
microsoft_kiota_authentication_oauth-0.8.0/lib/microsoft_kiota_authentication_oauth/oauth_access_token_provider.rb:66:in `block in get_authorization_token'

This happens here: https://github.com/microsoft/kiota-authentication-oauth-ruby/blob/main/lib/microsoft_kiota_authentication_oauth/oauth_access_token_provider.rb#L66

This could be addressed by simply checking if a refresh token is available here: https://github.com/microsoft/kiota-authentication-oauth-ruby/blob/main/lib/microsoft_kiota_authentication_oauth/oauth_access_token_provider.rb#L65

if token.expired? && !token.refresh_token.nil?

Then it would automatically request a new token instead of trying to refresh it.

Activity

  1. baywet commented on Oct 23, 2026

    @baywet
    Member

    Additional context from reviewing the current OAuthAccessTokenProvider#get_authorization_token implementation: the missing-refresh-token case is not the only issue in this control flow.

    Relevant code:

    • For an unexpired cached token, token.token is evaluated but does not exit the Fiber; execution continues to @token_request_context.get_token.
    • After a successful refresh!, the refreshed token is cached, but its string value is likewise discarded and another token acquisition follows.
    • For an expired token without a refresh token, refresh! raises before the code can request a replacement, as described in this issue.

    A network-free probe with a stub context and an unexpired OAuth2::AccessToken confirmed that two consecutive requests to the same allowed host call get_token twice (Ruby 3.3 in Docker).

    When fixing this, regression cases should distinguish:

    1. An unexpired cached token is returned without another acquisition.
    2. An expired token with a refresh token returns the refreshed token without a redundant acquisition.
    3. An expired token without a refresh token requests a new token rather than calling refresh!.

    Also see #190 for per-request default scopes. Cache reuse should remain consistent with the effective scopes when one provider is used across multiple allowed hosts.

    The current diffs in #187 and #189 do not change this caching/refresh block; this remains a separate follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions