Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doing RequestRefreshTokenAsync() with RefreshTokenRequest does not include the client_id parameter #1215

Closed
jozefizso opened this issue Apr 10, 2024 · 4 comments

Comments

@jozefizso
Copy link

Which version of Duende IdentityServer are you using?

I am using IdentityModel.Client v7.0.0

Which version of .NET are you using?

.NET 8.0

Describe the bug

I am trying a simple refresh token sample using IdentityModel client library. I have the ClientId for the application and a valid refresh token. When I use the RequestRefreshTokenAsync() method, server will return error "client_id" is required.

To Reproduce

Run this code:

var response = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
    Address = "https://auth.example.org/oauth2/token",

    ClientId = "valid-client-id",
    RefreshToken = "eyJhbGciO...redacted...bs_3h66A9VQ"
});

Expected behavior

The grant_type=refresh_token request should include the client_id and/or client_secret values.

See https://www.oauth.com/oauth2-servers/making-authenticated-requests/refreshing-an-access-token/

Log output/exception with stacktrace

Logging the HttpClient request and response data shows the requests includes the grant_type and refresh_token parameters only. The client_id parameter is missing.

Request:
Method: POST, RequestUri: 'https://auth.example.org/oauth2/token', Version: 1.1, Content: System.Net.Http.FormUrlEncodedContent, Headers:
{
  Accept: application/json
  Authorization: Basic MzcwM...redacted...mVlOm5vbmU=
  Content-Type: application/x-www-form-urlencoded
}
grant_type=refresh_token&refresh_token=eyJhbGciO...redacted...bs_3h66A9VQ

Response:
StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Connection: keep-alive
  Date: Wed, 10 Apr 2024 21:03:02 GMT
  Server: nginx
  Content-Type: application/json; charset=utf-8
  Content-Length: 75
}
{"error":"invalid_request","error_description":"\"client_id\" is required"}
@josephdecock
Copy link
Member

Thanks for an interesting question. Since we just released IdentityModel v7 yesterday, I was initially worried that we had a new bug, but after checking things more closely I don't think that's the case. The tl;dr is: set ClientCredentialStyle = ClientCredentialStyle.PostBody in the RefreshTokenRequest.

The client_id is not a parameter that is required by the specs at the token endpoint when you are refreshing a token. RFC 6749's first example of token refresh actually doesn't include it:

     POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded

     grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

Now, confidential clients are required to authenticate themselves, and that can be done with client_id and client_secret parameters in the post body. But it doesn't have to be that way - it is often done through the authorization http header instead. And while you are allowed to put the client id and secret into the post body, all the specs contain language discouraging this in favor of using an http header. That's probably a good idea because most systems treat the authentication header with a greater degree of sensitivity, so you'd be less likely to have a client secret logged or otherwise mishandled if it was kept in the authorization header. In your example, you appear to be using a public client - I don't see a client secret or client assertion at least. Assuming you're using a public client, there's no way to authenticate at all, no need to send the client_id parameter, and no client secret to send as the client_secret.

All that said, it looks like your identity provider is expecting you to use a client_id. (Side note: is the identity provider here actually IdentityServer? I don't think we produce the error_description you show, and I can make refresh requests that succeed and are configured like yours to IdentityServer). In any event, if your identity provider demands it, you can make IdentityModel send the client_id parameter by setting ClientCredentialStyle = ClientCredentialStyle.PostBody in the RefreshTokenRequest. This is a sort of degenerate case where there is no client secret or assertion, so you get only the client_id.

@josephdecock josephdecock self-assigned this Apr 11, 2024
@jozefizso
Copy link
Author

Thanks a lot for thorough explanation.

Yes, it's our OAuth2 server which requires client_id in the payload.

I will try the suggested setting. Unfortunately I wasn't able to find it in the documentation before.

@jozefizso
Copy link
Author

This solved the problem. Thanks for explanation, @josephdecock

@josephdecock
Copy link
Member

Great! If you run into more issues in IdentityModel, please create issues in its repo: https://github.com/IdentityModel/IdentityModel. I've created a few issues in that repo to update the docs (including documenting this policy option).

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

No branches or pull requests

2 participants