AuthenticationFailed raised after successfully getting Access Taken with OpenId Connect - oauth-2.0

I am currently working on an Asp.Net Core project that uses OpenId to handle user authentication to their Office 365 environment. Here is how I setup my OpenId/Cookies:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AccessDeniedPath = new PathString("/Home/Forbidden"),
CookieSecure = CookieSecurePolicy.SameAsRequest,
// The default setting for cookie expiration is 14 days. SlidingExpiration is set to true by default
ExpireTimeSpan = TimeSpan.FromHours(1),
SlidingExpiration = true
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions()
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
ClientId = Configuration.GetSection("AppSettings").GetValue<string>("ClientId"),
Authority = Configuration.GetSection("AppSettings").GetValue<string>("AzureADAuthority"),
CallbackPath = "/Dashboard",
PostLogoutRedirectUri = Configuration.GetSection("AppSettings").GetValue<string>("LogoutAuthority"),
SignInScheme = "Cookies",
TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false },
Events = new LhAuthenticationEvents(settings.Value),
SaveTokens = true,
ResponseType = "code id_token"
});
In my LhAuthentificationEvents, I use AuthorizationCodeReceived to successfully retrieve the user's access token, but for some reason, after exiting AuthorizationCodeReceived, AuthenticationFailed gets called.
public override Task AuthenticationFailed(AuthenticationFailedContext context)
{
return Task.FromResult(0);
}
Context contains the following exception:
Response status code does not indicate success: 401 (Unauthorized).
With the following stacktrace:
at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.d__21.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.d__19.MoveNext()

Related

Web API access token not refreshing after it's expired

I can refresh the access token when its valid, but after the expiry date I can't refresh it anymore. I have the exact same problem as him Web API refresh token not refreshing when access token is expired but I didn't find any solution.
That's how I generate the access token:
public async Task<string> GenerateAccessToken(User _User)
{
var userId = _User.Id.ToString();
var userName = _User.UserName;
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName)
};
var roles = await _userManager.GetRolesAsync(_User);
foreach (var role in roles) claims.Add(new Claim(ClaimTypes.Role, role));
var secret = "";
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddMinutes(10),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
After some debugging I found out this function gives me the error:
public ClaimsPrincipal GetPrincipalFromExpiredToken(string _Token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("")),
ValidateIssuer = false,
ValidateAudience = false
};
IdentityModelEventSource.ShowPII = true;
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(_Token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals("hs256", StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
To be more specific this line
var principal = tokenHandler.ValidateToken(_Token, tokenValidationParameters, out securityToken);
I receive the following response when I try to refresh it:
Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired. ValidTo: '04/25/2022 13:28:11', Current time: '04/25/2022 15:06:26'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateLifetime(Nullable`1 notBefore, Nullable`1 expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateLifetime(Nullable`1 notBefore, Nullable`1 expires, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at rentalAppAPI.BLL.Helper.TokenHelper.GetPrincipalFromExpiredToken(String _Token) in C:\Users\Alex\source\repos\rentalAppAPI\rentalAppAPI.BLL\Helper\ITokenHelper.cs:line 86
at rentalAppAPI.BLL.Managers.AuthManager.Refresh(RefreshModel refreshModel) in C:\Users\Alex\source\repos\rentalAppAPI\rentalAppAPI.BLL\Managers\AuthManager.cs:line 83
at rentalAppAPI.Controllers.AuthController.Refresh(RefreshModel model) in C:\Users\Alex\source\repos\rentalAppAPI\rentalAppAPI\Controllers\AuthController.cs:line 45
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
I looked up after this error
SecurityTokenExpiredException Class
Throw this exception when a received Security Token has expiration time in the past.
Changing ValidateLifetime = true, to false, if it still doesn't work, changing RequireExpirationTime to false as well.
In your var tokenValidationParameters = new TokenValidationParameters{}, you set the validate life time as true, that means when you try to refresh a valid access token, the token can be decoded and will return the principals correctly, but when you try to refresh an expired access token, since the token is expired, it can't be decoded.
When we write the refresh logic, we need to keep the ValidateAudience, ValidateIssuer, ValidateLifetime, RequireExpirationTime to false so that we can decode the access token anyway...

Redirect back to the ASP.NET Mvc Client after Sign-out from IdentityServer

I want to redirect back to my client after sign-out from local, then the IS4; My AspNetCore Mvc client works correctly and redirect back to the client after sign-out, but the AspNet Mvc (not Core) it doesn't.
here is my Startup.Configuration method:
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
SignInAsAuthenticationType = "Cookies",
Authority = "https://localhost:5000",
UseTokenLifetime = false,
// RedeemCode = true,
ClientId = "aspNet_client",
ClientSecret = "secret",
RedirectUri = "https://localhost:44343/sigin-oidc",
PostLogoutRedirectUri = "https://localhost:44343/signout-callback-oidc",
SaveTokens = true,
ResponseType = "code id_token",
Scope = "openid profile offline_access",
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = JwtClaimTypes.PreferredUserName,
RoleClaimType = JwtClaimTypes.Role,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = onAuthenticationFailed,
MessageReceived = onMessageReceived,
// AuthorizationCodeReceived = onAuthorizationCodeReceived
}
});
}
I used this method to sign-out:
public ActionResult SignOut()
{
Request.GetOwinContext().Authentication.SignOut();
return Redirect("/");
}
I used this method too:
public ActionResult SignOut()
{
System.Web.HttpContext.Current.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties
{
RedirectUri = "https://localhost:44343"
},
CookieAuthenticationDefaults.AuthenticationType,
OpenIdConnectAuthenticationDefaults.AuthenticationType
);
//"Cookies", "OpenIdConnect"
}
But not worked. So my question is:
How to automatic redirect back to my AspNetMvc Client after sign-out?
This was an error reported long time ago on IdentityServer3. It got fixed here by setting IdTokenHint on logout. In this case as we use IdentityServer4, we can implement similar fix manually on ASP.NET MVC app. Here is changes need to make:
on IdentityServer project set PostLogoutRedirectUris for the client:
new Client
{
ClientId = "aspNet_client",
//All other settings ...
PostLogoutRedirectUris = { "http://localhost:44343" },
},
On ASP.NET mvc application, set OpenIdConnectAuthenticationOptions - PostLogoutRedirectUri to the same value as step 1
Change Notifications - SecurityTokenValidated and RedirectToIdentityProvider to set IdTokenHint on logout
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
// other settings...
PostLogoutRedirectUri = "http://localhost:44343",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
return Task.FromResult(0);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var id_token_claim = n.OwinContext.Authentication.User.Claims.FirstOrDefault(x => x.Type == "id_token");
if (id_token_claim != null)
{
n.ProtocolMessage.IdTokenHint = id_token_claim.Value;
}
}
return Task.FromResult(0);
}
}
});
If you want to redirect automatically set AccountOptions - AutomaticRedirectAfterSignOut to true on IdentityServer, default value is false.
Implemented it myself here

How to use MVC4 Client with IdentityServer4?

Does anyone know how to have MVC 4 client app to use identityserver4 as auth provider?
I have tried the sample codes of identityserver3 but no success. Upon request to [Authorize] action it redirects to identityserver4 probably login end point and gives unknown error.
As far as I know, I am not able to define client at both identityserver4 'start-up.cs' and MVC client with OWIN's 'startup.cs'.
Update
The code from my IdentityServer4 app - MVC 4 Client Definition
// OpenID Connect hybrid flow and client credentials client (MVC)
new Client
{
ClientId = "mvc4",
ClientName = "MVC 4 Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:53173/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:53173/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
And the code from 'Startup.cs' of my MVC 4 app
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "http://localhost:5000/",
RequireHttpsMetadata = false,
ClientId = "mvc4",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = "openid profile api1 offline_access",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies",
});
}
Update 2
I changed the Startup.cs of my MVC 4 Client to:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
RedirectUri = "http://localhost:53173/signin-oidc",
ClientId = "mvc4",
ClientSecret = "secret",
ResponseType = "code id_token"
});
It now presents a login page, logs in the user and then the IdentityServer has gone into never ending loop:
Update 3
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
RedirectUri = "http://localhost:53173/signin-oidc",
ClientId = "mvc4",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = "openid profile api1 offline_access",
AuthenticationMode = AuthenticationMode.Active
});
}
As recommended added the scopes but still there is a loop; the request swings between MVC4 client and IdentityServer4.
Update 4
Solved - Check my answer.
I finally got it working.
Firstly, there is a bug (Katana Bug #197) in the OWIN which makes it to handle the tokens rather 'awkwardly'. So a workaround is nuget package Kentor.OwinCookieSaver by Kentor. One will need to install at the MVC4 Client.
Thereafter, modify the client configuration as under:-
new Client
{
ClientId = "mvc4",
ClientName = "MVC 4 Web Client",
AllowedGrantTypes = {
GrantType.Hybrid,
GrantType.ClientCredentials
},
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:53173/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:53173/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
Modify the Configuration of 'Startup.cs' at MVC4 client as under
public void Configuration(IAppBuilder app)
{
app.UseKentorOwinCookieSaver();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
RedirectUri = "http://localhost:53173/signin-oidc",
ClientId = "mvc4",
ClientSecret = "secret",
ResponseType = OpenIdConnectResponseType.CodeIdTokenToken,
Scope = "openid profile api1 offline_access",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = notification =>
{
notification.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));
notification.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", notification.ProtocolMessage.AccessToken));
return Task.FromResult(0);
},
RedirectToIdentityProvider = notification =>
{
return Task.FromResult(0);
}
}
});
Rebuild Solution >> Clean and Run. Now you can use IdentityServer4 oidc for MVC4 Client.
I would recommend you review all URLs and make sure that they are all identical and there is no any extra / in Identity or client configuration.
One more thing, I can't see you scope in "Update 2".

Why does Authorize attribute not cause HTTP 401 for unauthorized requests with JWTs

I have a Web API app that I am hopefully protecting with JWTs as follows, in Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddMvc();
}
I also have app.UseAuthentication(); in the Startup.Configure method. When I make an unauthorized request using Postman, I get nothing as a response, just a blank body, and when I allow anonymous on the same action, I get the expected result. I also get the expected result if I make an authorized request. I would expect an HTTP 401 when I make an unauthorized request, not just nothing happening.
Where am I going wrong?
Not sure. Perhaps you need to configure the challenge scheme. Below works for me (.NET Core 2.x).
public static IServiceCollection AddJwtValidation(this IServiceCollection services)
{
IServiceProvider sp = services.BuildServiceProvider();
ConfigRoot = sp.GetRequiredService<IConfigurationRoot>();
tokenAudience = ConfigRoot["JwtToken:Audience"];
tokenIssuer = ConfigRoot["JwtToken:Issuer"];
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Audience = tokenAudience;
options.ClaimsIssuer = tokenIssuer;
options.TokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
IssuerSigningKeyResolver = MyIssuerSigningKeyResolver,
// Validate the JWT lifetime. Note that if 'exp' is present, then it is validated. If it
// is missing, then the lifetime validation is not done.
ValidateLifetime = true,
RequireExpirationTime = false,
LifetimeValidator = null, //MyLifetimeValidator,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = tokenIssuer,
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = tokenAudience,
};
// Override the default ValidateToken method with a custom method.
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new MyJwtSecurityTokenHandler());
});
return services;
}

Why is cookie's expiration date is 'Session' when using Owin

My web application is MVC5. I'm calling an url of IdentityServer4 application to authenticate user when logging in.
Here is the method ConfigureAuth of Startup class in my application
public void ConfigureAuth(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
var authority = LayeredConfiguration.GetValue("HydraInsuranceWeb-UserManagement-Authority");
var redirectUri = LayeredConfiguration.GetValue("HydraInsuranceWeb-UserManagement-RedirectUri");
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = "Cookies",
SlidingExpiration = false,
ExpireTimeSpan = System.TimeSpan.FromMinutes(2),
CookieName = "MyTestCookie"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = authority,
ClientId = AuthConstants.InsuranceWebClientId,
Scope = "openid profile user.management hydra.eventhistory.api",
RedirectUri = redirectUri,
ResponseType = "code id_token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
try
{
var transformedHydraIdentity = new HydraIdentityBuilder(n.AuthenticationTicket.Identity)
.AllowSecurityAdmin()
.IncludeRoleProfiles()
.IncludeIdToken(n.ProtocolMessage.IdToken)
.IncludeStandardClaims()
.Build();
n.AuthenticationTicket = new Microsoft.Owin.Security.AuthenticationTicket(
transformedHydraIdentity,
n.AuthenticationTicket.Properties);
}
catch (Exception ex)
{
n.HandleResponse();
n.Response.Redirect("/Error/NoAuthorization");
DiagnosticService.Writer.AddError("Authentication Error", ex);
}
return Task.FromResult(0);
},
}
});
}
After logging in, the cookie's expiration is always "Session", not the current time plus 2 minutes.
But my expectation is the cookie's expiration is a specific datetime, it should be current time plus 2 minutes. If user doesn't operate in 2 minutes, jump to the login page.
Has anyone known this issue? Please tell me how to investigate or debug to know why cookie's expiration is changed.
And there are 2 cookies: .AspNet.Cookies and MyTestCookie. Which cookie is used to authenticate user?
You need to set IsPersistent to True when signing in.
AuthenticationManager.SignIn(new AuthenticationProperties{ IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(30)}, userIdentity);

Resources