3

I'm using Identity Server 4. When my API receives a token expiry (IDX10223: Lifetime validation failed which is expected) at the point where it validates access to an API, it appears to redirect to the logout page rather than just return 401. I'm trying to find out where this is controlled since this results in the client being sent the HTML for the logout page which isn't desirable.

The API code has:

[Authorize]
public IActionResult API() { 
}

The API and IdentityServer also run on the same server. Is this a function of AuthorizeFilter and is the answer to create a custom authorize attribute?

Relevant parts of the log file

2017-08-02T10:27:28.8366946+01:00 0HL6PFUCMUMHG [INF] Request starting HTTP/1.1 GET http://localhost:55742/v3.0/User/recent?conversationId=21680095&sortFlag=2 (e5be5b71)
2017-08-02T10:27:28.9641697+01:00 0HL6PFUCMUMHG [INF] Failed to validate the token "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3NTMyYzdlZDVmZjc1MGQ5YWE0ZjYyNjQ0YjczMjY5IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDE2NjIxNDUsImV4cCI6MTUwMTY2NTc0NSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1NTc0MiIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjU1NzQyL3Jlc291cmNlcyIsImNpeEFwaTMiXSwiY2xpZW50X2lkIjoiY2l4Rm9ydW1zRGV2Iiwic3ViIjoic3BhbG1lciIsImF1dGhfdGltZSI6MTUwMTY2MDk0MSwiaWRwIjoibG9jYWwiLCJyb2xlIjoiZnVsbCIsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJjaXhBcGkzIiwib2ZmbGluZV9hY2Nlc3MiXSwiYW1yIjpbInB3ZCJdfQ.jcGNgeGSY72SRODAnWQWJI5XXRZaM0SSO3DAFp7QQeDzXbxNDIgkDqRUkQAJoGU52C4svk6DQwKGrsFDATzI52g8iuD8JAugaOen4DfEx_g6CP3JdImUn6aT379rjH5_d1ePXVaSwBMU9L3q1mkA20EotWA6mcIdYZw54Xvsp-TGnWbMKAL-yv8_Vh7gQn-_vBy7sfTB4s_37SZtSmpi7ig7WPa2XbAVwNN_vmApL0ZgP8QsotyTiIDEloXov5XYkAe7JvunpHyaATg8RCirNu6zp38yBHzkJ0IJh7BJZ47IDImE-AxfvZY8_EW6m1LJosVjgrjSnG5nIFu_mPvASA". (f3081a27)
Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '08/02/2017 09:22:25'
Current time: '08/02/2017 09:27:28'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateLifetime(Nullable1 notBefore, Nullable1 expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateLifetime(Nullable1 notBefore, Nullable1 expires, JwtSecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwt, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.d__1.MoveNext()
2017-08-02T10:27:28.9701726+01:00 0HL6PFUCMUMHG [INF] "Bearer" was not authenticated. Failure message: "IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '08/02/2017 09:22:25'
Current time: '08/02/2017 09:27:28'." (48071232)
2017-08-02T10:27:28.9761723+01:00 0HL6PFUCMUMHG [INF] Authorization failed for user: null. (a4ab1676)
2017-08-02T10:27:28.9831738+01:00 0HL6PFUCMUMHG [INF] Authorization failed for the request at filter '"Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter"'. (8b6446cb)
2017-08-02T10:27:28.9951674+01:00 0HL6PFUCMUMHG [INF] Executing ChallengeResult with authentication schemes ([]). (f3dca807)
2017-08-02T10:27:29.0231698+01:00 0HL6PFUCMUMHG [INF] AuthenticationScheme: "Bearer" was challenged. (d45f1f38)
2017-08-02T10:27:29.0281709+01:00 0HL6PFUCMUMHG [INF] Executed action "API3.Controllers.UserController.Recent (API3)" in 52.7575ms (afa2e885)
2017-08-02T10:27:29.0371704+01:00 0HL6PFUCMUMHG [INF] Request finished in 200.5214ms 401 (15c52c40)
2017-08-02T10:27:29.1141679+01:00 0HL6PFUCMUMHH [INF] Request starting HTTP/1.1 OPTIONS http://localhost:55742/connect/endsession?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A62784%2Fsignout-callback-oidc&id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3NTMyYzdlZDVmZjc1MGQ5YWE0ZjYyNjQ0YjczMjY5IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDE2NjIxNDUsImV4cCI6MTUwMTY2MjQ0NSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1NTc0MiIsImF1ZCI6ImNpeEZvcnVtc0RldiIsIm5vbmNlIjoiNjM2MzcyNTg5NDM0Njg3NzM4LlpUbGxaRGxpTVdZdE5EVmlZUzAwWkdNeExXRTNOVFl0WldRM1pUY3lZV0ptTVRZelpHRTBNRGd4WmpjdFl6QTRPQzAwTldZMUxUa3daVGt0TjJRMU1UQmhNR0ZpWW1NNCIsImlhdCI6MTUwMTY2MjE0NSwiYXRfaGFzaCI6ImFXMFB6eEdPVVAza1pIcGFHR1RYLVEiLCJzaWQiOiI3NDdhODA5ZTE5NjZmOGY5Y2U5ZWExZDcwYmQ3Y2M2MSIsInN1YiI6InNwYWxtZXIiLCJhdXRoX3RpbWUiOjE1MDE2NjA5NDEsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.btHqukWt1stWHFyUCqwJu8PS9e9hPUH2N6oqCqtpp_bjalPK5Ub-4uJTwp0_CYiuPt-Atl_XI1H3BI9p7ENIu6YHLgB6Hyr1l7G6e8S64HMsaKTEcQaSSVjUWwF4U1IV6YFVuZ5VjLR-M5FK4mYIcT-xsdDF4kkJTvs1lCqldeg9exLTnca8FsC3E__eIjIhRd8oYHhMUazkQ34FsyqYFESAgRHqIl9IITfeoPYNFMRtuz3P09zCKPCSSrg9t-0UecJ5ccwf1cTfU3tLv9j1rknBYjFjeDah38dHhJLjAEfH5r_fytKEr44kwB2xeu030x1vw1vDPiHi3fbRc72pGA&state=CfDJ8AdS2WFU56FEplHeLVGDdfl3IlfIaWI00-bPygyle9qi0Hnqf-jsZ5pqtwO-dVU2ujH5OkhQRpQyn0OpsXCRVVeul2QTBwi1q40y1QwiXTuftRutLkxADzyrjqrzshvHz479t13919PZIj09GABirToAXtrMUXHu6J0eExJcF8eB (e5be5b71)
2017-08-02T10:27:29.1401709+01:00 0HL6PFUCMUMHH [INF] Request finished in 33.4959ms 204 (15c52c40)
2017-08-02T10:27:29.1501730+01:00 0HL6PFUCMUMHI [INF] Request starting HTTP/1.1 GET http://localhost:55742/connect/endsession?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A62784%2Fsignout-callback-oidc&id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3NTMyYzdlZDVmZjc1MGQ5YWE0ZjYyNjQ0YjczMjY5IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDE2NjIxNDUsImV4cCI6MTUwMTY2MjQ0NSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1NTc0MiIsImF1ZCI6ImNpeEZvcnVtc0RldiIsIm5vbmNlIjoiNjM2MzcyNTg5NDM0Njg3NzM4LlpUbGxaRGxpTVdZdE5EVmlZUzAwWkdNeExXRTNOVFl0WldRM1pUY3lZV0ptTVRZelpHRTBNRGd4WmpjdFl6QTRPQzAwTldZMUxUa3daVGt0TjJRMU1UQmhNR0ZpWW1NNCIsImlhdCI6MTUwMTY2MjE0NSwiYXRfaGFzaCI6ImFXMFB6eEdPVVAza1pIcGFHR1RYLVEiLCJzaWQiOiI3NDdhODA5ZTE5NjZmOGY5Y2U5ZWExZDcwYmQ3Y2M2MSIsInN1YiI6InNwYWxtZXIiLCJhdXRoX3RpbWUiOjE1MDE2NjA5NDEsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.btHqukWt1stWHFyUCqwJu8PS9e9hPUH2N6oqCqtpp_bjalPK5Ub-4uJTwp0_CYiuPt-Atl_XI1H3BI9p7ENIu6YHLgB6Hyr1l7G6e8S64HMsaKTEcQaSSVjUWwF4U1IV6YFVuZ5VjLR-M5FK4mYIcT-xsdDF4kkJTvs1lCqldeg9exLTnca8FsC3E__eIjIhRd8oYHhMUazkQ34FsyqYFESAgRHqIl9IITfeoPYNFMRtuz3P09zCKPCSSrg9t-0UecJ5ccwf1cTfU3tLv9j1rknBYjFjeDah38dHhJLjAEfH5r_fytKEr44kwB2xeu030x1vw1vDPiHi3fbRc72pGA&state=CfDJ8AdS2WFU56FEplHeLVGDdfl3IlfIaWI00-bPygyle9qi0Hnqf-jsZ5pqtwO-dVU2ujH5OkhQRpQyn0OpsXCRVVeul2QTBwi1q40y1QwiXTuftRutLkxADzyrjqrzshvHz479t13919PZIj09GABirToAXtrMUXHu6J0eExJcF8eB (e5be5b71)
2017-08-02T10:27:29.1591678+01:00 0HL6PFUCMUMHI [DBG] CORS request made for path: "/connect/endsession" from origin: "http://localhost:62784" but rejected because invalid CORS path (2bd60464)
2017-08-02T10:27:29.1601680+01:00 0HL6PFUCMUMHI [DBG] Request path "/connect/endsession" matched to endpoint type EndSession (b74352b0)
2017-08-02T10:27:29.1611695+01:00 0HL6PFUCMUMHI [DBG] Mapping found for endpoint: EndSession, creating handler: "IdentityServer4.Endpoints.EndSessionEndpoint" (67a7e1a4)
2017-08-02T10:27:29.1671697+01:00 0HL6PFUCMUMHI [INF] Invoking IdentityServer endpoint: "IdentityServer4.Endpoints.EndSessionEndpoint" for "/connect/endsession" (f7642de5)
2017-08-02T10:27:29.1791689+01:00 0HL6PFUCMUMHI [DBG] Processing signout request for "anonymous" (02b52180)
2017-08-02T10:27:29.1871689+01:00 0HL6PFUCMUMHI [DBG] Start end session request validation (8af7241a)
2017-08-02T10:27:29.1931688+01:00 0HL6PFUCMUMHI [DBG] Start identity token validation (5fcce1be)
2017-08-02T10:27:29.1961697+01:00 0HL6PFUCMUMHI [DBG] Client found: "myclient" / "ClientName" (bb32274b)
2017-08-02T10:27:29.2021686+01:00 0HL6PFUCMUMHI [DBG] Calling into custom token validator: "IdentityServer4.Validation.DefaultCustomTokenValidator" (467e6def)
2017-08-02T10:27:29.2091719+01:00 0HL6PFUCMUMHI [DBG] Token validation success
"{
"ClientId": "myclient",
"ClientName": "ClientName",
"ValidateLifetime": false,
"Claims": {
"nbf": 1501662145,
"exp": 1501662445,
"iss": "http://localhost:55742\",
"aud": "myclient",
"nonce": "636372589434687738.ZTllZDliMWYtNDViYS00ZGMxLWE3NTYtZWQ3ZTcyYWJmMTYzZGE0MDgxZjctYzA4OC00NWY1LTkwZTktN2Q1MTBhMGFiYmM4",
"iat": 1501662145,
"at_hash": "aW0PzxGOUP3kZHpaGGTX-Q",
"sid": "747a809e1966f8f9ce9ea1d70bd7cc61",
"sub": "spalmer",
"auth_time": 1501660941,
"idp": "local",
"amr": "pwd"
}
}" (997b5fde)
2017-08-02T10:27:29.2361731+01:00 0HL6PFUCMUMHI [INF] End session request validation success
"{
"ClientId": "myclient",
"ClientName": "ClientName",
"SubjectId": "unknown",
"PostLogOutUri": "http://localhost:62784/signout-callback-oidc\",
"State": "CfDJ8AdS2WFU56FEplHeLVGDdfl3IlfIaWI00-bPygyle9qi0Hnqf-jsZ5pqtwO-dVU2ujH5OkhQRpQyn0OpsXCRVVeul2QTBwi1q40y1QwiXTuftRutLkxADzyrjqrzshvHz479t13919PZIj09GABirToAXtrMUXHu6J0eExJcF8eB",
"Raw": {
"post_logout_redirect_uri": "http://localhost:62784/signout-callback-oidc\",
"id_token_hint": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3NTMyYzdlZDVmZjc1MGQ5YWE0ZjYyNjQ0YjczMjY5IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDE2NjIxNDUsImV4cCI6MTUwMTY2MjQ0NSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1NTc0MiIsImF1ZCI6ImNpeEZvcnVtc0RldiIsIm5vbmNlIjoiNjM2MzcyNTg5NDM0Njg3NzM4LlpUbGxaRGxpTVdZdE5EVmlZUzAwWkdNeExXRTNOVFl0WldRM1pUY3lZV0ptTVRZelpHRTBNRGd4WmpjdFl6QTRPQzAwTldZMUxUa3daVGt0TjJRMU1UQmhNR0ZpWW1NNCIsImlhdCI6MTUwMTY2MjE0NSwiYXRfaGFzaCI6ImFXMFB6eEdPVVAza1pIcGFHR1RYLVEiLCJzaWQiOiI3NDdhODA5ZTE5NjZmOGY5Y2U5ZWExZDcwYmQ3Y2M2MSIsInN1YiI6InNwYWxtZXIiLCJhdXRoX3RpbWUiOjE1MDE2NjA5NDEsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.btHqukWt1stWHFyUCqwJu8PS9e9hPUH2N6oqCqtpp_bjalPK5Ub-4uJTwp0_CYiuPt-Atl_XI1H3BI9p7ENIu6YHLgB6Hyr1l7G6e8S64HMsaKTEcQaSSVjUWwF4U1IV6YFVuZ5VjLR-M5FK4mYIcT-xsdDF4kkJTvs1lCqldeg9exLTnca8FsC3E__eIjIhRd8oYHhMUazkQ34FsyqYFESAgRHqIl9IITfeoPYNFMRtuz3P09zCKPCSSrg9t-0UecJ5ccwf1cTfU3tLv9j1rknBYjFjeDah38dHhJLjAEfH5r_fytKEr44kwB2xeu030x1vw1vDPiHi3fbRc72pGA",
"state": "CfDJ8AdS2WFU56FEplHeLVGDdfl3IlfIaWI00-bPygyle9qi0Hnqf-jsZ5pqtwO-dVU2ujH5OkhQRpQyn0OpsXCRVVeul2QTBwi1q40y1QwiXTuftRutLkxADzyrjqrzshvHz479t13919PZIj09GABirToAXtrMUXHu6J0eExJcF8eB"
}
}" (8a893fca)
2017-08-02T10:27:29.2381718+01:00 0HL6PFUCMUMHI [DBG] Success validating end session request from "myclient" (e682f926)
2017-08-02T10:27:29.3242185+01:00 0HL6PFUCMUMHI [INF] Request finished in 176.7054ms 302 (15c52c40)
2017-08-02T10:27:29.3301739+01:00 0HL6PFUCMUMHJ [INF] Request starting HTTP/1.1 OPTIONS http://localhost:55742/account/logout?logoutId=ee47b7921636973f6c169a7c7472f7fc (e5be5b71)
2017-08-02T10:27:29.3351682+01:00 0HL6PFUCMUMHJ [INF] Request finished in 8.0939ms 204 (15c52c40)
2017-08-02T10:27:29.3421709+01:00 0HL6PFUCMUMHK [INF] Request starting HTTP/1.1 GET http://localhost:55742/account/logout?logoutId=ee47b7921636973f6c169a7c7472f7fc (e5be5b71)
2017-08-02T10:27:29.3431703+01:00 0HL6PFUCMUMHK [DBG] CORS request made for path: "/account/logout" from origin: "http://localhost:62784" but rejected because invalid CORS path (2bd60464)
2017-08-02T10:27:29.3531756+01:00 0HL6PFUCMUMHK [INF] "Bearer" was not authenticated. Failure message: "No token found." (48071232)
2017-08-02T10:27:29.4273094+01:00 0HL6PFUCMUMHK [INF] Executing action method "API3.Authentication.Account.AccountController.Logout (API3)" with arguments (["ee47b7921636973f6c169a7c7472f7fc"]) - ModelState is Valid (ba7f4ac2)
2017-08-02T10:27:29.4881722+01:00 0HL6PFUCMUMHK [INF] AuthenticationScheme: "idsrv" signed out. (d3f50c8d)
Steven Palmer
  • 302
  • 2
  • 11
  • It doesn't look as if anybody really knows and other solutions involving custom Authorize attributes are pre-ASP.Net Core. However I did find https://stackoverflow.com/questions/31464359/how-do-you-create-a-custom-authorizeattribute-in-asp-net-core which suggests replacing the Authorize attribute with a simpler custom version based on claims and which doesn't implement the 401 redirect to the login/logout page. I'm testing this at the moment and I'll update here if it turns out to the the correct answer. – Steven Palmer Aug 03 '17 at 07:22
  • Nope. It is still hitting the login page when the token expires. I suspect the only solution is to detect in the login page whether it is coming from the API and, if so, return a 401 from there directly. – Steven Palmer Aug 03 '17 at 12:17

2 Answers2

0

OK, from experimentation and research, it appears that in ASP.NET Core 1.1 you should NOT use the [Authorize] attribute to protect an API where you want to avoid the redirect to the login screen when the token expires. Instead, create your own custom attribute or check the claims for the user via User.Claims and explicitly return 401 from your API function.

Steven Palmer
  • 302
  • 2
  • 11
0

How about modifying OnRedirectToLogin Event and avoid the redirect when it's an API request?

.AddCookie("Cookies", options =>
    options.Events.OnRedirectToLogin = ctx =>
    {
        if (ctx.Request.Path.StartsWithSegments("/api") &&
            ctx.Response.StatusCode == int)System.Net.HttpStatusCode.OK)
        {
            ctx.Response.StatusCode =(int)System.Net.HttpStatusCode.Unauthorized;
        }
        else
        {
            ctx.Response.Redirect(ctx.RedirectUri);
        }
        return Task.FromResult(0);
    }
)
KavehG
  • 294
  • 1
  • 2
  • 11