golang / oauth2

Go OAuth2
https://golang.org/x/oauth2
BSD 3-Clause "New" or "Revised" License
5.31k stars 979 forks source link

User Principal OAuth2 (Three Legged, Authorization Code) does not include Refresh Token(s) #675

Open YvanJAquino opened 11 months ago

YvanJAquino commented 11 months ago

Hi there,

I'm seeing some unexpected behavior while interacting with oauth2.Config & oauth2.Token objects.

My client targets a Google Cloud OAuth Client ID (via the Oauth Consent Screens service AKA OAuth Brands). My client uses a web app type credential which exists in the following format:

{
  "web": {
      "client_id": "...",
      "project_id": "...",
      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
      "token_uri": "https://oauth2.googleapis.com/token",
      "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
      "client_secret": "...",
      "redirect_uris": [
          "http://localhost",
          "https://oauth2-authorizer-service"
      ]
  }
}

Creating a config from this always works fine. I then update the redirect URL, generate a randomized state string, and generate an AuthCodeURL, specifying the oauth2.AccessTypeOffline option.

/ Local Fetch token initiates a request from the localhost; the user is responsible for
// providing the state and auth code.
func (tm *TokenManager) LocalFetchToken(ctx context.Context) (err error) {
    // Generate an opaque identifier by hex-digesting random bytes.
    stateBuf := make([]byte, 32)
    if _, err = rand.Reader.Read(stateBuf); err != nil {
        return
    }
    oauth_state := hex.EncodeToString(stateBuf)
    authCodeURL := tm.config.AuthCodeURL(oauth_state, oauth2.AccessTypeOffline)
    fmt.Printf("Please go to the following link in your browser to generate an auth code:\n\t%s\n", authCodeURL)
    var code, state string

    fmt.Print("Authorization code:")
    fmt.Scanln(&code)
    fmt.Print("\nAuthorization state:")
    fmt.Scanln(&state)

    if oauth_state != state {
        return fmt.Errorf("states do not match")
    }

    token, err := tm.config.Exchange(ctx, code)
    if err != nil {
        return
    }
    fmt.Printf("Token Refresh Token: %s\n", token.RefreshToken)
    tm.token = token
    return
}

This is not producing Refresh tokens even with the oauth2.AccessTypeOffline option. In the past, this used to issue Refresh tokens.

According to the documentation on oauth2.AccessTypeOffline documentation:

    // AccessTypeOnline and AccessTypeOffline are options passed
    // to the Options.AuthCodeURL method. They modify the
    // "access_type" field that gets sent in the URL returned by
    // AuthCodeURL.
    //
    // Online is the default if neither is specified. If your
    // application needs to refresh access tokens when the user
    // is not present at the browser, then use offline. This will
    // result in your application obtaining **a refresh token the**
    // **first time your application exchanges an authorization**
    // **code for a user.**
    AccessTypeOnline  AuthCodeOption = SetAuthURLParam("access_type", "online")
    AccessTypeOffline AuthCodeOption = SetAuthURLParam("access_type", "offline")

Regards,

redboxstudio commented 10 months ago

Perhaps I encountered a similar problem, only I have slightly different conditions. I use keycloak and log in using the code, but in response I receive a token without refreshToken image however, there are examples where it is clear that refreshToken is provided image

redboxstudio commented 10 months ago

I created a new client and it now has a button to enable token renewal

YvanJAquino commented 10 months ago

It's worth noting that I need to prompt for consent - IE I need to do the ENTIRE flow.