The "normal" flow is working fine and the request to the oauth server is made through a form:
Content-Type: application/x-www-form-urlencoded
Form Data
grant_type: authorization_code
client_id: XXXXXXXXXXXX
code_verifier: aa304440a5dc497d2f6807c2ae537eb0de772d84e202395cb1191f5f6536kfVOwGT
code: uz0f_tuk24m8TiuexmuUtKUvwO2nlANYjtUAAAEs
redirect_uri: http://localhost:4200
but when the id_token expires, and the silent renew process is called, the request to the oauth server is made through a text/plan:
Content-Type: text/plain
Request Payload
grant_type=authorization_code&client_id=XXXXXXXXXXX&code_verifier=73e0579882e1954adcab4997dffd3a2ef6ccdb99132c0e53c9ed37d8f73atQQQcZ3&code=T2uXdeizLINhWTSUSiGwDNVWrNkLm5W94cwAAAEs&redirect_uri=http://localhost:4200/silent-renew.html
and the following error is returned from the server:
POST https://oauthserver.com/as/token.oauth2 400 (Bad Request)
error: {error_description: "grant_type is required", error: "invalid_request"}
If I change the request (using postman) to use the content type: application/x-www-form-urlencoded, it works.
@marcio-fe-s thanks for reporting. This seems like a bug. Which STS do you use? Do you use refresh tokens or iframe silent renew?
Greeting Damien
@damienbod I have the same problem. My STS is IdentityServer4, authenticating against Azure AD.
This is how I configure my clients:
var authClient = new Client()
{
ClientId = client.ApplicationName,
ClientName = client.ApplicationLabel,
AccessTokenLifetime = tokenExpirySeconds,
AccessTokenType = AccessTokenType.Jwt,
AllowAccessTokensViaBrowser = true,
AllowedCorsOrigins = listOfCorsUrls,
AllowedGrantTypes = GrantTypes.Code,
AllowOfflineAccess = true,
AllowedScopes = scopes,
IdentityTokenLifetime = tokenExpirySeconds,
PostLogoutRedirectUris = urls,
RedirectUris = redirectUrls,
RequireClientSecret = false,
RequireConsent = false,
RequirePkce = true,
RefreshTokenExpiration = TokenExpiration.Sliding,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
UpdateAccessTokenClaimsOnRefresh = true,
};
Here is the cURL for the connect call, with codes and such removed:
curl 'https://auth.myauthserver.co.za/connect/token' \
-H 'Connection: keep-alive' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Device-Type: Desktop' \
-H 'DNT: 1' \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Origin: https://dev.myapp.co.za' \
-H 'Sec-Fetch-Site: same-site' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Referer: https://dev.myapp.co.za/' \
-H 'Accept-Language: en-US,en;q=0.9' \
--data-raw 'grant_type=authorization_code&client_id=CLIENTID&code_verifier=CODEVERIFIER&code=CODE&redirect_uri=https://dev.myapp.co.za/assets/silent-renew.html' \
--compressed
This is the response:
{"error":"unauthorized_client"}
The client_id is definitely valid though.
Here is how it comes about in my case:
I'll check why the Content-Type: text/plain, this is the problem I think, should be Content-Type: application/x-www-form-urlencoded
@CobusKruger @marcio-fe-s do you have kind of a sample repo to reproduce? The Content-Type has to be set anywhere. Can you reproduce this in a smaller app anyhow?
Thanks.
@FabianGosebrink It's in our production app (problem, right?), so I'll have to whittle it down for you. But it's based on this example: https://github.com/damienbod/angular-auth-oidc-client/tree/master/projects/sample-code-flow-http-config.
I definitely don't specify the content type explicitly anywhere.
Hey @CobusKruger Yes, when facing such a problem try to boil it down to a smaller repo with only in it what needed and provide that to us please. Then we can link our lib code and see where the content type is being added.
Have you tried to switch the identity server (there is a demo one or you use mine from the examples)? In your config we can not see what values you are configuring. For example: if your redirect URL from the request doesn't match one of the ones provided in Identity server, you'll get that error. Can you provide the config with the values and not the variables?
Thanks
@FabianGosebrink The values you don't see are all valid. They are read from a config web service. As stated before, simply refreshing the page causes the sign-in to work. I _think_ it happens because the silent renew triggers before the library has processed the query string. When I refresh, the silent renew happens after the redirect and then it all works very well (including the silent renew).
I should also add that I did not have this problem with 10.4.x.
@CobusKruger @FabianGosebrink
I鈥檓 seeing the same thing in a small sample I did using KeyCloak. I鈥檒l push a sample in a few hours.
Ok, so after some research I think I've worked out what's happening.
In the sample below there is an TokenInterceptor which appends the token to outgoing requests. Having this in place is causing a CORS error when the OAuth server sends it response.
I should add that my issue does not appear to be directly related to the reported issue. Refactoring my interceptor seems to have addressed the issue.
Most helpful comment
@CobusKruger @FabianGosebrink
I鈥檓 seeing the same thing in a small sample I did using KeyCloak. I鈥檒l push a sample in a few hours.