Unable to Validate JWT or access_token in API with IdentityServer4 - c#

I'm working on building out some new functionality where we have an existing system. Our goal is to have different Angular SPA's communicate with APIs to present a new interface for our legacy app. A team has set up IdentityServer4 to use the legacy application for credentials verification.
The SPA, written in Angular 7, uses the implicit flow to authenticate. Response type is "token id_token" I've been able to get this flow to work insomuch as I can have an unauthenticated user come to the site, get kicked over to the IdentityServer authentication screens, log in with correct credentials, and then redirected back to the app. When I get the token back the claims object has a single value for aud, the client_id for the SPA, for example, spa-client. The access_token and id_token are both a JWT with RS256 as the signature method. Lastly, I've created an interceptor in the SPA to send the JWT (in actuality it just uses whatever value is sent back as the access_token) as the "Bearer" token on API requests.
I have now created a .NET Core 2.2 WebApi app to build out a service API to be consumed by the SPA. This app uses the IdentityServer4.AccessTokenValidation package to "protect" the API I've read the section in the IdentityServer4 docs. In my ConfigureServices method I've written the following to configure auth:
services
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
if (string.IsNullOrWhiteSpace(serviceConfiguration.Authentication.Authority)) throw new ConfigurationException("", nameof(serviceConfiguration.Authentication.Authority), "An authority must be defined.");
if (string.IsNullOrWhiteSpace(serviceConfiguration.Authentication.ApiName)) throw new ConfigurationException("", nameof(serviceConfiguration.Authentication.ApiName), "An api name must be defined.");
options.Authority = serviceConfiguration.Authentication.Authority;
options.ApiName = serviceConfiguration.Authentication.ApiName;
if (!string.IsNullOrWhiteSpace(serviceConfiguration.Authentication.ApiSecret))
{
options.ApiSecret = serviceConfiguration.Authentication.ApiSecret;
}
options.RequireHttpsMetadata = false;
options.SupportedTokens = SupportedTokens.Both;
}
serviceConfiguration is a strongly type representation of my JSON appsettings.json
My understanding is that with options.SupportedTokens = SupportedTokens.Both will check the JWT and do introspection.
Now that all this is setup I've taken the simple example ValuesController that is created by the API template and added the [Authorize] attribute to it. Doing a simple GET request to this endpoint in Postman returns a 401 as I would expect.
Next I try logging into the SPA and going to a test page which will do the same but with the interceptor will have the bearer token, the access_token, or basically the JWT. I still get a 401 from the service.
Looking at the logs output by the service I don't see anything specific that is helpful:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 OPTIONS http://localhost:5001/api/values info:
Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 OPTIONS http://localhost:5001/api/values info:
Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
CORS policy execution successful. info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
CORS policy execution successful. info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 61.1352ms 204 info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 61.1272ms 204 info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5001/api/values application/json info:
Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5001/api/values application/json info:
Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
CORS policy execution successful. info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
CORS policy execution successful. info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'Examples.TestApp.API.Web.Controllers.ValuesController.Get
(Examples.TestApp.API.Web)' info:
Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'Examples.TestApp.API.Web.Controllers.ValuesController.Get
(Examples.TestApp.API.Web)' info:
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "Get", controller = "Values"}. Executing action
Examples.TestApp.API.Web.Controllers.ValuesController.Get
(Examples.TestApp.API.Web) info:
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "Get", controller = "Values"}. Executing action
Examples.TestApp.API.Web.Controllers.ValuesController.Get
(Examples.TestApp.API.Web) info:
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed. info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'. info:
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'. info:
Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Executing ChallengeResult with authentication schemes (). info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Executing ChallengeResult with authentication schemes (). info: IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler[12]
AuthenticationScheme: Bearer was challenged. info: IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler[12]
AuthenticationScheme: Bearer was challenged. info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action Examples.TestApp.API.Web.Controllers.ValuesController.Get
(Examples.TestApp.API.Web) in 166.3038ms info:
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action Examples.TestApp.API.Web.Controllers.ValuesController.Get
(Examples.TestApp.API.Web) in 162.8827ms info:
Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'Examples.TestApp.API.Web.Controllers.ValuesController.Get
(Examples.TestApp.API.Web)' info:
Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'Examples.TestApp.API.Web.Controllers.ValuesController.Get
(Examples.TestApp.API.Web)' info:
Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 459.6677ms 401 info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 473.7648ms 401
Setting the logging to Trace didn't reveal anything else.
I did try going to Postman to see if I could do introspection on the token manually. I set up authorization to basic and set the Username to "test-api", the api id, and the password that I set in its client secret. In the body a set it to x-www-form-urlencoded with a key of "token" and the value the access_token. I get a 200 OK with claims along with "active: true". Seems like that should be good, right?
I don't own the IdentityServer implementation so I'm not quite sure what's going on at that end except for being able to log into the admin UI. I've logged into the admin UI for the identity server and double checked the settings for the client and the API. The client gets granted a "api" scope and the API gets the same scope "api". This is shown in the scope that is sent back in the introspection response as well as the token that I get from the client.
I've also tried setting the options.SupportedTokens = SupportedTokens.Reference and options.SupportedTokens = SupportedTokens.JWT. Neither had any change. :(
What am I doing wrong? Is this a configuration issue or me being an idiot (probably the latter).
EDIT: RE access_token and id_token
I was incorrect in the original question. The tokens are different (I need to look at more than just the header portion).
The audiences are weird. I've never paid attention to a possible difference here when decoding. My powers of perception must suck!
The access token has aud: "http:///resources"
The id_token has aud: "spa-client"

The access_token and id_token are both the same and are a JWT with RS256 as the signature method.
The access_token and id_token should not the same . For id_token the audclaim in token should be the name of the client , but in access token , your api name should be include in the aud , so that your api resource could validate that the access token is issued to make "my" api calls. You can use online tool like jwt.io to decode the claims in JWT tokens . Since you haven't post the configuration of client & IDS4 , you should check the response type,scope ... If using Fiddler to trace the request , it should like :
GET /connect/authorize/callback?client_id=js&redirect_uri=http%3A%2F%2Flocalhost%3A5003%2Fcallback.html&response_type=id_token%20token&scope=openid%20profile%20api1&state=xxxxxxxxxxxxxxx&nonce=xxxxxxxxxxxxxxxxxxx
In addition , from document : http://docs.identityserver.io/en/latest/topics/grant_types.html
For JavaScript-based applications, Implicit is not recommended anymore. Use Authorization Code with PKCE instead.
You can click here for code flow code sample .
Update :
the audience of access token should also include api name , apart from "http:///resources :
You could click here for code samples .

Related

AuthorizeForScopes 'x-ReturnUrl' not being redirected to - MsalUiRequiredException

In Microsoft.Identity.Web I am using the [AuthorizeForScopes(ScopeKeySection = "Azure:Scope")] attribute to for incremental consent.
Because I am calling the decorated controller with an Ajax request upon erroring with a MsalUiRequiredException a redirect causes a Cors error. Now, this is expected and is documented here. In these docs it says:
This occurred when the user's authorization was expired, or incremental consent or conditional access was needed. The controller action was throwing a MsalUiRequiredException which created a request of type "xhr / Redirect" and returned a 302 which resulted in the CORS issue due to the redirect.
Microsoft.Identity.Web 1.2.0 brings support of this scenario by extracting the return url from the ajax custom header tp return to the user agent after the challenge is satisfied.
(I'm using 1.24.0)
It suggests adding a new custom header in the http request, which I've done:
headers:
{
'Content-Type': 'application/json',
'x-ReturnUrl': '/logout',
}
However, it doesn't redirect me to this url, I'm still getting a Cors error on the redirect. It is not redirecting me to the return url I've provided in the headers. In the response headers of the initial request I can see the location property which does not match.

OAuth2 in memory token loss due to server application restart

I encountered this problem in test stage of my application. Client set expiration date of token for very long time (19 years or so), so we wouldn't request new token often during tests. But After a while it came out that token has already expired (after random time).
The problem was server application being restarted/updated, resulting in in-memory tokens lost and rendering my simple check for expires_in not working:
if (_currentToken.ExpirationDate < DateTime.Now.AddMinutes(1))
{
_currentToken = GetToken();
}
How would I secure such scenario? It might as well happen on production, but hopefully more rarely due to less application restarts and shorter token time. Unfortunately I don't have an access to server side authorization settings and tokens won't be persited in any storage.
I would like avoid calling some dummy action on server to check if it returns 401 unauthorized before every action.
IMO it's server side's responsibility to validate token and decide whether the request with token could access specific protected resources . So as #armagedescu suggested , just send the token when performing token request , server side will check the claims like expire time , issuer ... and also check the signature . If token is expired , it will return 401 status code , and the OAuth 2.0 bearer token spec adds error, error_description, and error_uri attributes to the WWW-Authenticate header for reporting additional error information :
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
error="invalid_token",
error_description="The access token expired"
Then on client side you can check the errors , and refresh access token to perform another token request .

Webex guest issuer integration with widgets -- Create team web api issue

I am trying to create a Webex team using the following process. But not working. I had created JWT token and exchanged it for access token which I had passed in to the Authorization header in the API call. But I keep getting the error message that I mentioned below. And another fact is I can execute the same API with GET method which returns the number of teams I had.I had followed steps in the webex developer site. But I can't create the team.
Request URL:
https://api.ciscospark.com/v1/teams
Request Method:
POST
The following is the header that I passed to the API
Accept:
application/json
Content-Type:
application/json
Authorization:
Bearer eyJhbGciOiJSUzI1NiJ9.eyJtYWNoaW5lX3R5cGUiOiJhcHB1c2VyIiwiY2x1c3RlciI6IlBGODQiLCJwcml2YXRlIjoiZXlKamRIa2lPaUpLVjFRaUxDSmxibU1pT2lKQk1USTRRMEpETFVoVE1qVTJJaXdpWVd4bklqb2laR2x5SW4wLi4zRnp6Mkh4c2k0TnI4Z24yMk03cm1BLjhtUnRIYUhOWXBkQ2ZydUR1d0s0MnJNMlhZTEZvNHJKN0NTTkJSQ21kZFRnNTdzUzZ1cUVxcjZFakQ0Yk1JOUY5NFluOWx6VGFWcnRHVkpLOGo2djNzX1VBaTJ2Rnk5TDl3Z05SaUlOczB3NU5yTkxLLUx3TDNQdGhiMzJEYzF2TlhGZkRtakFmSjlOZG1QeEdMQVYxWU9EVnhnNE5MbnpHNDczY24zaTJlbWlfdVBzbk9LRTZHYjEwMVBkekdCSVVQaGRwMkY4bVZhd3hzN2pxbUdzVmEtdXJaYXhpN243emZyaXBjUE5HZjNhSURWb011LVkzaXN0ak03U2s1Qno4TXcwNFZueXpGYWt4LWw0WHlfdUVJalpDeXNsNU56TGNTWkVHc2hJRnJsVHZmdzZZU1J4elV0cXJ2Y3hrX09GT01ZeFRsSDVvSXVsSXdXRWpVQ2o5RTBWZVJMNHB2Z3BQYTRkOE1zNHR6Tmh3cmNpRV9Hd0Fob0tmOE9oMEVQZTM4MmdaOW5wNFJvOWRVSnBnRGszR3NhYWx4ZmJubFRmMmt6RWVjQlJCa0NnSEJXRjhUZXNpdWZWV2djWktZX0E1dXh0aTlMSTB0Umk5X3ZsNjJuTy1uMlh5dm1VZlQxUUxLNmthamJwU01renRUOHdLRkxxLVdTVk54TUJlMWRid094Ni1nN0c3bldLZ1J5ZEpEVEdrazMtRW9wbVRURnZhTEk2dkp6S3NGTzlGRUFJVld1ZFd2RWlWWjVXdVN3dS5yS3RJTzZ6aVpFVE1XV3ZhOEVZaElnIiwicmVmZXJlbmNlX2lkIjoiODlkNjdlZDUtOTU5My00YjBjLWI0MGEtMGFjOTE0OTZlZjZkIiwiaXNzIjoiaHR0cHM6XC9cL2lkYnJva2VyLndlYmV4LmNvbVwvaWRiIiwidG9rZW5fdHlwZSI6IkJlYXJlciIsImNsaWVudF9pZCI6IkMzMTE3NzIzYTBhNGM5ODVhOGJkNmRkYTc2Zjc3NjZjNTEzMjRiNmY0MWJmNGZlZjFjMmM4Mjc4NGExZjI5NzVjIiwidXNlcl90eXBlIjoibWFjaGluZSIsInRva2VuX2lkIjoiQWFaM3IwWkRabU1XWXhaVEF0T0dGbU15MDBOR05oTFdKa09EY3RZVFZrTmpjM056STBNamhoTkRkaU9XVXlNbU10TkdOaiIsInVzZXJfbW9kaWZ5X3RpbWVzdGFtcCI6IjIwMTkwNTI3MDQxNzQ4LjEwMloiLCJyZWFsbSI6IjM5ZWQ0NDRmLTU2NGUtNDFlNC05N2VkLWI4MWFmNzM0ZWVkMCIsImNpc191dWlkIjoiNjJkZWU2NjktNDE5OC00N2YzLWE2ODgtNzA4YWQxM2VjNDc3IiwiZXhwaXJ5X3RpbWUiOjE1NTg5NTIyNzA0NTB9.VNUms24YsiwNbU_KN0Y7v1osAilq1MMiIQn6DRnyNitJAgeTLuyrwiYM0JIaox-dQxQKBp897uAj9vksg0ik2-j2MddcRmx4gsVF787uw7VpCIiZNNME346__xddvmmttna7IErBTz84hqZsnvSlmKLpus4lQs_k9I8ZHTsuPJ9FRXPry6ZAmPjDk1uWGGWFXyOSgH-VkDj9hY9Fy_6r7MKIn0YV21dxIKaZVZ6DMo1NSJM68I-90Vv-QbQY3gBeH25ZXDNDDBJg6-woMF2QPk_N8zrL8G1WPRXxGAA_lqGr_qGq65CfOwuqGikpLsRtjhWU4sYDX3xrK918W08CFw
This is the parameter I passed to the API
name=hearingteam
This is the server response
{"message":"The request could not be understood by the server due to malformed syntax.","errors":[{"description":"The request could not be understood by the server due to malformed syntax."}],"trackingId":"ROUTER_5CEB64F0-FC8D-01BB-05C2-7AA62A6905C2"}
The API requires parameters to be passed in JSON format.
Eg : JSON.stringyfy({"name":"hearingteam"});

Fetch OAuth token from ebay api

I'm developing a web application to to fetch data from 'https://api.ebay.com/sell/analytics/v1/traffic_report'
I've a ebay developer account with
ClientId: MyClientId
ClientSecret: MyClientSecret
AppId: MyAppId
To achive this, I need a OAuth token
To get OAuth Token I do the following steps.
I browse the url bellow
https://signin.ebay.com/authorize?client_id=MyClientId&redirect_uri=RuName&response_type=code&state=analytics&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope%2Fsell.analytics.readonly
It goes to
https://signin.ebay.com/ws/eBayISAPI.dll?VAppJanessa&reqinput=reqinput - auto generate
after sign in it redirects to 'Grant application access to MySiteDisplayName' page.
After I Agree it redirects to 'https://my_site.com/?state=analytics&code=code'
I've preapared a post request after collecting the code
I've executed the post request using POSTMAN like bellow
Post URL: 'https://api.ebay.com/identity/v1/oauth2/token'
Headers:
cache-control: no-cache
Content-Type: application/x-www-form-urlencoded
Authorization: Basic Base64 value of 'MyClientId:MyClientSecret'
Body:
grant_type: authorization_code
redirect_uri: RuName
code: code returned from previous request
After post request I've got the result
{
"error": "invalid_grant",
"error_description": "the provided authorization grant code is invalid or was issued to another client"
}
Please tell me what is missing or wrong.
Looks like clientId and appId you are sending don't match each other, probably one is a production one and another being a developer one. Happened with me once.
If there are multiple apps being registered, each with its own appId under the same clientId, many times the client Secret Gets missed. Try checking the correct combination of all three.

Twitter OAuth "Could not authenticate you" when I specify a callback url

I'm having an issue with Twitter OAuth 1.0 during the request_token stage
If I set a oauth_callback of oob (or even omit the callback url) then it works fine, but then of course doesnt redirect the user to my web app afterwards.
N.B. I may be going about this the wrong way. I just want to specify a URL that the client browser should be directed to after having authorized the OAuth request on Twitter
As soon as I change the oauth_callback to my app's callback url, I get the error "Could not authenticate you"
I have setup the callback url in the app settings on twitter to exactly the same url as the one I am settings in the oauth_callback parameter of the request_token process which FYI is an HTTPS base url i.e. no path specified, https://www.example.com/
Any assistance would be appreciated
UPDATE
This request works (no callback url specified at all):
POST https://api.twitter.com/oauth/request_token HTTP/1.1
Authorization: OAuth oauth_consumer_key="----myconsumerkey----", oauth_nonce="r70s1926", oauth_signature="iFGmXEpDav0lVpge9Ls9ACGI6r0%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1503053275", oauth_version="1.0"
This request does not (callback url specified, same as twitter app setting):
POST https://api.twitter.com/oauth/request_token HTTP/1.1
Authorization: OAuth oauth_callback="https%3A%2F%2Fwww.example.com", oauth_consumer_key="----myconsumerkey----", oauth_nonce="707n8282", oauth_signature="0KWzeJwQ%2FNMfmdZ%2Bt0zNEU4g3Ag%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1503053306", oauth_version="1.0"
The request above returns:
{"errors":[{"code":32,"message":"Could not authenticate you."}]}
Because exclusion or a setting of oob works it sounds like an encoding problem within the signature generation.
Check that the oauth_callback url is double encoded within your signature base string which should look something like:
POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttps%253A%252F%252Fwww.example.com%26oauth_consumer_key%3Dmyconsumerkey%26oauth_nonce%3D707n8282%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1503057157%26oauth_version%3D1.0

Categories

Resources