2

Using .Net Core 2.1 and Identity Server 4

I am using Identity Server 4 to authenticate a login on my MVC application. The process works, it won't let me get to pages that are marked [Authorize] until I sign in. And it redirects me to my IS4 server to log in, once logged in I am redirected back to my MVC app and able to see the protected pages.

However, moving forward I wanted to also call an API with this client and am having issues.

I think the root of the issue is that I cannot access my access_token or id_token. They always return null when I call them.

I am using the below code in my Razor page and it always returns null for each of the tokens.

@using Microsoft.AspNetCore.Authentication

@{
    var claims = User.Claims;
    var idt = await ViewContext.HttpContext.GetTokenAsync("id_token");
    var at = await ViewContext.HttpContext.GetTokenAsync("access_token");
    var rt = await ViewContext.HttpContext.GetTokenAsync("refresh_token");
}

<dt>id_token</dt>
<dd>@idt</dd>

<dt>access token</dt>
<dd>@at</dd>

<dt>refresh token</dt>
<dd>@rt</dd>

My MVC startup looks like this

services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<IdentityContext>()
                .AddDefaultTokenProviders();

            services.AddAuthentication(options =>
                {
                    options.DefaultScheme = "Cookies";
                    options.DefaultChallengeScheme = "oidc";
                })
                .AddCookie("Cookies")
                .AddOpenIdConnect("oidc", options =>
                    {
                        options.SignInScheme = "Cookies";
                        options.Authority = "http://localhost:5000";
                        options.RequireHttpsMetadata = false;
                        options.ClientId = "mvc";
                        options.ClientSecret = "secret";
                        options.ResponseType = "code id_token";
                        options.SaveTokens = true;
                        options.GetClaimsFromUserInfoEndpoint = true;
                        options.Scope.Add("api1");
                        options.Scope.Add("offline_access");
                        options.Scope.Add("openid");
                        options.Scope.Add("email");
                    });

And my IS4 startup looks like this

services.AddIdentityServer()
            .AddAspNetIdentity<ApplicationUser>()
            .AddConfigurationStore(configDb =>
            {
                configDb.ConfigureDbContext = db => db.UseNpgsql(Configuration.GetConnectionString("IdentityServer4ConfigurationContext"),
                    sql => sql.MigrationsAssembly(typeof(IdentityServer4Startup).GetTypeInfo().Assembly.GetName().Name));
            })
            .AddOperationalStore(operationDb =>
            {
                operationDb.ConfigureDbContext = db => db.UseNpgsql(Configuration.GetConnectionString("IdentityServer4PersistedGrantContext"),
                     sql => sql.MigrationsAssembly(typeof(IdentityServer4Startup).GetTypeInfo().Assembly.GetName().Name));
            })
            .AddSigningCredential("CN=localhost");

I am confused as to how IS4 is logging in and working properly. Looking at the logs everything is issuing tokens and authenticating the tokens on all requests.

Here is a bit of the logs that are produced

[13:05:20 INF] Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token
[13:05:20 VRB] Processing token request.
[13:05:20 DBG] Start token request.
[13:05:20 DBG] Start client validation
[13:05:20 DBG] Start parsing Basic Authentication secret
[13:05:20 DBG] Start parsing for secret in post body
[13:05:20 DBG] Connection id "0HLIABES76N39", Request id "0HLIABES76N39:00000003": started reading request body.
[13:05:20 DBG] Connection id "0HLIABES76N39", Request id "0HLIABES76N39:00000003": done reading request body.
[13:05:20 DBG] Parser found secret: PostBodySecretParser
[13:05:20 DBG] Secret id found: mvc
[13:05:20 DBG] mvc found in database: True
[13:05:20 DBG] Secret validator success: HashedSharedSecretValidator
[13:05:20 DBG] Client validation success
[13:05:20 VRB] Calling into token request validator: IdentityServer4.Validation.TokenRequestValidator
[13:05:21 DBG] Start token request validation
[13:05:21 DBG] Start validation of authorization code token request
[13:05:21 DBG] F5F9HpNIqo4A+pLlz3KUHAOT3suUMARiwjA+AiMiYKQ= found in database: True
[13:05:21 DBG] removing F5F9HpNIqo4A+pLlz3KUHAOT3suUMARiwjA+AiMiYKQ= persisted grant from database
[13:05:21 DBG] Validation of authorization code token request success
[13:05:21 VRB] Calling into custom request validator: IdentityServer4.Validation.DefaultCustomTokenRequestValidator
[13:05:21 INF] Token request validation success
{
  "ClientId": "mvc",
  "ClientName": "MVC Client",
  "GrantType": "authorization_code",
  "AuthorizationCode": "4f5aae4aa4569dbe537ba5146f5f6dc2e7854308481844804b646cb9ecaec26c",
  "Raw": {
    "client_id": "mvc",
    "client_secret": "***REDACTED***",
    "code": "4f5aae4aa4569dbe537ba5146f5f6dc2e7854308481844804b646cb9ecaec26c",
    "grant_type": "authorization_code",
    "redirect_uri": "http://localhost:61000/signin-oidc"
  }
}
[13:05:21 VRB] Calling into token request response generator: IdentityServer4.ResponseHandling.TokenResponseGenerator
[13:05:21 VRB] Creating response for authorization code request
[13:05:21 DBG] mvc found in database: True
[13:05:21 DBG] Found ["openid", "profile", "email"] identity scopes in database
[13:05:21 DBG] Found ["api1"] API scopes in database
[13:05:21 VRB] Creating access token
[13:05:21 DBG] Getting claims for access token for client: mvc
[13:05:21 DBG] Getting claims for access token for subject: 081d6afc-b10d-4a61-ab09-fdaf8a614129
[13:05:21 VRB] Creating JWT access token
[13:05:21 DBG] Creating refresh token
[13:05:21 DBG] Setting an absolute lifetime: 2592000
[13:05:21 DBG] pHf7GHw+2sCYm+0gR8FRGOgxyXQIxC37sqp58NZeSiw= not found in database
[13:05:21 DBG] mvc found in database: True
[13:05:21 DBG] Found ["openid", "profile", "email"] identity scopes in database
[13:05:21 DBG] Found ["api1"] API scopes in database
[13:05:21 VRB] Creating identity token
[13:05:21 DBG] Getting claims for identity token for subject: 081d6afc-b10d-4a61-ab09-fdaf8a614129 and client: mvc
[13:05:21 DBG] In addition to an id_token, an access_token was requested. No claims other than sub are included in the id_token. To obtain more user claims, either use the user info endpoint or set AlwaysIncludeUserClaimsInIdToken on the client configuration.
[13:05:21 VRB] Creating JWT identity token
[13:05:21 VRB] Identity token issued for mvc (MVC Client) / no subject: eyJhbGciOiJSUzI1NiIsImtpZCI6IkVDN0UyNjgyOTk4QkZDQjVFRTk3MUJDNzVGMTQxQkNGNTZEMjQyNDAiLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3SDRtZ3BtTF9MWHVseHZIWHhRYnoxYlNRa0EifQ.eyJuYmYiOjE1NDIyMTg3MjEsImV4cCI6MTU0MjIxOTAyMSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoibXZjIiwibm9uY2UiOiI2MzY3NzgxNTUxMjg2NTg2MjYuWWpjMk1XSTVNREV0WVdVeE5pMDBPVGM0TFRsbE5UTXRaR05oTXpCa01USXlNbUl4TkRVd1kyUTVNMk10WkRFNFlpMDBNVGN4TFRrek9USXRNV0V6WWpReFptWmxNVFZrIiwiaWF0IjoxNTQyMjE4NzIxLCJhdF9oYXNoIjoicGczLUszbjh3akZlenZiZi1FYXl1USIsInNpZCI6IjA0NWU2Mzg5MTc4ZDg2MTRiNTc4MmMzYWY4NThhOGZjIiwic3ViIjoiMDgxZDZhZmMtYjEwZC00YTYxLWFiMDktZmRhZjhhNjE0MTI5IiwiYXV0aF90aW1lIjoxNTQyMjE4NzE5LCJpZHAiOiJsb2NhbCIsImFtciI6WyJwd2QiXX0.hxVLqfAzmYamg89yhsWoYChzvUqrWFARuc0pqXYrRPP2wrfgVQ834PMHUtEk0BtqJTVXNzOgP38x3iiL3k7rw3P-dsVfBygiNSbDNQRYAjPCxSl4U7SNP0_1U6gbay3WZhRkntzAvvhOYrACghyB37DoT7EYvwnwOGYDUp0zpABoNaB4WuCwmQHV3vxkkLRUbz6uo3QSOyAxrvIVfEOXrOX-QafVcnyVjca1kWJronXRF4VWHhRDxx00j_SEuFaoXNZWA4aTyZ5zySZBEOaIQeS-d2ExE5xqdIWKd3VDu9dZ8nQmEikK4VNQVN-AGdzFXLTeT9BQjYKoqbG4_OAtdA
[13:05:21 VRB] Refresh token issued for mvc (MVC Client) / no subject: 034c4f17df4fe6879ef7b10cfa07b2d84a0a748d20e9249a856296db58b44fea
[13:05:21 VRB] Access token issued for mvc (MVC Client) / no subject: eyJhbGciOiJSUzI1NiIsImtpZCI6IkVDN0UyNjgyOTk4QkZDQjVFRTk3MUJDNzVGMTQxQkNGNTZEMjQyNDAiLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3SDRtZ3BtTF9MWHVseHZIWHhRYnoxYlNRa0EifQ.eyJuYmYiOjE1NDIyMTg3MjEsImV4cCI6MTU0MjIyMjMyMSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkxIl0sImNsaWVudF9pZCI6Im12YyIsInN1YiI6IjA4MWQ2YWZjLWIxMGQtNGE2MS1hYjA5LWZkYWY4YTYxNDEyOSIsImF1dGhfdGltZSI6MTU0MjIxODcxOSwiaWRwIjoibG9jYWwiLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJhcGkxIiwib2ZmbGluZV9hY2Nlc3MiXSwiYW1yIjpbInB3ZCJdfQ.a1Vy2yRh98ApFVe1jqK7jwTOoaMfnxWOwu8Qr4ENh_vtty7LPElzngnUrs-nw0Xt3C1oMVvy5Ok7ZB9iiMdHPm6YaSnpBkyvivjFv4ttBtsmdj-qB0nR9o1S8UgbiaEP3rhpK-1hflYGGI1cm7lOucVgi9HYmBKudvWsKa_7b44nbrriit4v9wF_cA4r95xDEfr8ZIOu2QsGhYSN9mQTRE_okj_aoigYVEr_Nw7_5BGV1cE8v7NmmREcX4kHJaBhQBpAJ9D4s9QK1at0sGJ_3AmNC97CzOiDkeC_mPWuOXSxfK9i_XgzHVDYXEZwuFaTowwmwTzTlHnnLZ_D6FjiRw
[13:05:21 DBG] Token request success.
[13:05:21 VRB] Invoking result: IdentityServer4.Endpoints.Results.TokenResult
[13:05:21 DBG] Connection id "0HLIABES76N39" completed keep alive response.
[13:05:21 INF] Request finished in 430.9773ms 200 application/json; charset=UTF-8
[13:05:21 INF] Request starting HTTP/1.1 GET http://localhost:5000/connect/userinfo
[13:05:21 VRB] All hosts are allowed.
[13:05:21 DBG] The request path /connect/userinfo does not match a supported file type
[13:05:21 DBG] AuthenticationScheme: Identity.Application was not authenticated.
[13:05:21 DBG] AuthenticationScheme: Identity.Application was not authenticated.
[13:05:21 DBG] Request path /connect/userinfo matched to endpoint type Userinfo
[13:05:21 DBG] Endpoint enabled: Userinfo, successfully created handler: IdentityServer4.Endpoints.UserInfoEndpoint
[13:05:21 INF] Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo
[13:05:21 DBG] Start userinfo request
[13:05:21 DBG] Bearer token found in header
[13:05:21 VRB] Calling into userinfo request validator: IdentityServer4.Validation.UserInfoRequestValidator
[13:05:21 VRB] Start access token validation
[13:05:21 DBG] mvc found in database: True
[13:05:21 DBG] mvc found in database: True
[13:05:21 DBG] Calling into custom token validator: IdentityServer4.Validation.DefaultCustomTokenValidator
[13:05:21 DBG] Token validation success
{
  "ValidateLifetime": true,
  "AccessTokenType": "Jwt",
  "ExpectedScope": "openid",
  "Claims": {
    "nbf": 1542218721,
    "exp": 1542222321,
    "iss": "http://localhost:5000",
    "aud": [
      "http://localhost:5000/resources",
      "api1"
    ],
    "client_id": "mvc",
    "sub": "081d6afc-b10d-4a61-ab09-fdaf8a614129",
    "auth_time": 1542218719,
    "idp": "local",
    "scope": [
      "openid",
      "profile",
      "email",
      "api1",
      "offline_access"
    ],
    "amr": "pwd"
  }
}
[13:05:21 VRB] Calling into userinfo response generator: IdentityServer4.ResponseHandling.UserInfoResponseGenerator
[13:05:21 DBG] Creating userinfo response
[13:05:21 DBG] Scopes in access token: openid profile email api1 offline_access
[13:05:21 DBG] Scopes in access token: openid profile email api1 offline_access
[13:05:21 DBG] Found ["openid", "profile", "email"] identity scopes in database
[13:05:21 DBG] Requested claim types: sub updated_at locale zoneinfo birthdate gender website picture family_name name profile preferred_username nickname middle_name given_name email email_verified
[13:05:21 DBG] Scopes in access token: openid profile email api1 offline_access
[13:05:21 DBG] Found ["openid", "profile", "email"] identity scopes in database
[13:05:21 INF] Profile service returned to the following claim types: sub name given_name family_name email email_verified website preferred_username
[13:05:21 DBG] End userinfo request
[13:05:21 VRB] Invoking result: IdentityServer4.Endpoints.Results.UserInfoResult
[13:05:21 DBG] Connection id "0HLIABES76N39" completed keep alive response.
[13:05:21 INF] Request finished in 256.1036ms 200 application/json; charset=UTF-8

Any help would be appreciated.

Here is the ValidatedAuthorizeRequest which IS4 issues and contains the Client information

[13:21:00 INF] ValidatedAuthorizeRequest
{
  "ClientId": "mvc",
  "ClientName": "MVC Client",
  "RedirectUri": "http://localhost:61000/signin-oidc",
  "AllowedRedirectUris": [
    "http://localhost:61000/signin-oidc",
    "http://localhost:5000/signin-oidc"
  ],
  "SubjectId": "081d6afc-b10d-4a61-ab09-fdaf8a614129",
  "ResponseType": "code id_token",
  "ResponseMode": "form_post",
  "GrantType": "hybrid",
  "RequestedScopes": "openid profile api1 offline_access email",
  "State": "CfDJ8EzT-5PsjT5Htj2FYGOkFvypng74D54aal5fl_xHTuLrCVRVCQ7hdh7iEhwSe6bnkjRaLEH_heDufsxa6c59f0cHT-00AfhAu-4hOymVu8eIOhjcT5rDuMox5V616aTPJI7U5sYaav8Jhxn_8FS13PxXbwWCRK09stEQuo9eqNUgkaIHdIgZmvyo_wGhAD7W_kBNUvQHMLew6jfYjBFwKG6yy6io6Vo2IYlzBQCoQlX2Vs9iHRg7QNx2aXiObfhQGFht8gVh-HpeW_LbrSnlaOGyIG2bVbipE1hIx312JsmDi2aznaRSJ7awEnOVrLY74g",
  "Nonce": "636778164429105771.N2E2YmVmY2QtODlmNS00NWI1LTllOGYtOTY0YjIzYjdmNDQwZDAyMDhlYmQtNGVjYS00MzM0LWI0OGQtZGYxNzM4ZDE1ZmU2",
  "SessionId": "9c530abab1c6e8a9a3efc48ba989ddf8",
  "Raw": {
    "client_id": "mvc",
    "redirect_uri": "http://localhost:61000/signin-oidc",
    "response_type": "code id_token",
    "scope": "openid profile api1 offline_access email",
    "response_mode": "form_post",
    "nonce": "636778164429105771.N2E2YmVmY2QtODlmNS00NWI1LTllOGYtOTY0YjIzYjdmNDQwZDAyMDhlYmQtNGVjYS00MzM0LWI0OGQtZGYxNzM4ZDE1ZmU2",
    "state": "CfDJ8EzT-5PsjT5Htj2FYGOkFvypng74D54aal5fl_xHTuLrCVRVCQ7hdh7iEhwSe6bnkjRaLEH_heDufsxa6c59f0cHT-00AfhAu-4hOymVu8eIOhjcT5rDuMox5V616aTPJI7U5sYaav8Jhxn_8FS13PxXbwWCRK09stEQuo9eqNUgkaIHdIgZmvyo_wGhAD7W_kBNUvQHMLew6jfYjBFwKG6yy6io6Vo2IYlzBQCoQlX2Vs9iHRg7QNx2aXiObfhQGFht8gVh-HpeW_LbrSnlaOGyIG2bVbipE1hIx312JsmDi2aznaRSJ7awEnOVrLY74g",
    "x-client-SKU": "ID_NETSTANDARD1_4",
    "x-client-ver": "5.2.0.0"
  }
}
A. Hasemeyer
  • 1,452
  • 1
  • 18
  • 47
  • Is the client grant type set as Hybrid ?, Can you post the client configuration ? – warrior Nov 14 '18 at 18:22
  • Yes the client is hybrid, I am not using in memory clients so I am not sure of a good way to post them since all of the properties are spread across tables. I will add into the Question the `ValidatedAuthorizeRequest` which has some of this information in it. – A. Hasemeyer Nov 14 '18 at 18:24
  • and is the AllowAccessTokensViaBrowser Set to true ? – warrior Nov 14 '18 at 18:27
  • Does the controller action that caused that Razor page have an `Authorize` attribute on it? What claims is it showing for `User.Claims`? – sellotape Nov 14 '18 at 18:32
  • AllowAccessTokensViaBrowser was set to false, I updated it to true still no tokens. – A. Hasemeyer Nov 14 '18 at 18:34
  • It does have Authorize on the controller and it returns the claims: sub, AspNet.Identity.SecurityStamp, name, given_name, family_name, email, email_verified, website, address, preferred_username, idp, amr, auth_time – A. Hasemeyer Nov 14 '18 at 18:34
  • I can even see the tokens in the log, I confirm them with jwt.io that they are valid tokens. – A. Hasemeyer Nov 14 '18 at 18:40
  • 1
    Could be [this bug](https://github.com/aspnet/Security/issues/1765), seeing as you're on 2.1; try the workaround mentioned there and see if that helps for now. – sellotape Nov 14 '18 at 18:43
  • Thank you for the suggestions. I have looked into these and it seems like the `[Authorize]` does not send the token or `Request.Headers["Authorization"]`. I suspect this is because I am using `AspNetIdentity` which has tagged the User as Authenticated and it only checks that. Do any of you know how to make a request to IS4 to retrieve the token passing either the `State` or `Id` of the user? – A. Hasemeyer Nov 14 '18 at 19:48
  • `[Authorize]` (in the context above) is for your MVC server's action, to ensure that a security challenge happens, and hence your `User` is authenticated _in your MVC app_. If you want your MVC code to call an API that is protected by the same Identity Server, on behalf of the authenticated user, you need to explicitly add an `Authorization: bearer ` header to the request message you send to the API. There are ways to do this more automatically but they aren't built it as standard. – sellotape Nov 14 '18 at 20:49
  • Yeah it looks like its working now, that bug you mentioned seemed to be the big issue. `var apiUrl = "http://localhost:62000/test/api"; var authenticate = await HttpContext.AuthenticateAsync("Cookies"); var accessToken = authenticate.Ticket.Properties.GetTokenValue("access_token"); var client = new HttpClient(); client.SetBearerToken(accessToken); var response = await client.GetAsync(apiUrl); return Json(response.Content.ReadAsStringAsync());` Thanks – A. Hasemeyer Nov 14 '18 at 21:06

0 Answers0