ASP.NET Core Identity Settings Not Working - identityserver3

I've an implementation of Identity Server 4 with ASP.NET Identity. I asked an earlier question about how I would apply certain login rules and received an answer explaining how I could add some options in Startup.cs. Here's what I added to the ConfigureServices method:
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Password.RequiredLength = 9;
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = false;
options.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
The password rules seem to work, but the lockout rules have no effect. Is there something I need to enable?

Not sure how I missed this. The lockout feature happens as part of the sign-in process in the PasswordSignInAsync method on the SignInManager. The line of code I needed to change is part of the Login method in the AccountController:
SignInManager.PasswordSignInAsync(
model.Email,
model.Password,
model.RememberLogin,
lockoutOnFailure: true); // <- HERE

Related

Identity server stuck in redirect loop

I'm using identityserver4 for single sign-on. For most of the time application function smoothly but intermittently we face a redirect loop issue which becomes a show stopper for us until we restart's our app service. The page goes on loading continuously before finally showing a 'Bad request - Request Too Long' page with message: HTTP Error 400. The size of the request headers is too long. If we check the network tab, we can see that the application is looping between the identity server and client application redirect sign in pages. The application insight tells us that the client app gives a 401 on his home/index page and then a 302 on the signin-oidc url, then goes to the identity server connect/token, then connect/userinfo endpoints to get claims and comes back to the client home/index page to again get a 401. The loop continues (Identity server says user is authenticated while client says it is not). We are unable to find a fix for this since long. Any help is appreciated. Attaching the client side configuration for reference.
Findings
Our client app is an mvc app & we have used Session's & TempData in few area's. This areas are the triggering point of the redirect issue. What we have observed is, when the client initially login the authentication cookie is created (Cookie Name: AudDiscoveryAuth) and I could see it being passed in header for each request made to the controller actions. But once the user visit's any such area where we have used Session/TempData and Log out or any other user tries to login, Identity server successfully authenticates the user also the userendpoint to retrieve the details is being invoked however the cookie itself is not being created and is missing in every request to the Index/Home action method hence the redirect loop. Wondering what could be hampering in issuing cookie when using session variable elsewhere in the application or is their a setting missing.
Also in every redirect the occurrence of OpenIdConnect.nonce.XXX cookie is incremented. Once the count of OpenIdConnect.nonce.XXX reaches more then a certain level we get the bad request error page
public void Configuration(IAppBuilder app)
{
string baseClientAddress = ConfigurationManager.AppSettings["ApplicationUrl"];
int slidingExpiryHrs = Convert.ToInt32(ConfigurationManager.AppSettings["SlidingExpiryHrs"]);
int slidingExpiryMins = Convert.ToInt32(ConfigurationManager.AppSettings["SlidingExpiryMins"]);
TimeSpan expireTimeSpan = new TimeSpan(slidingExpiryHrs, slidingExpiryMins, 0);
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationType,
CookieName = "AudDiscoveryAuth",
ExpireTimeSpan = expireTimeSpan,
SlidingExpiration = true
});
app.UseOpenIdConnectAuthenticationPatched(new OpenIdConnectAuthenticationOptions
{
ClientId = "ratingspro.web",
Authority = IdsvrConstants.BaseAddress,
RedirectUri = baseClientAddress + "signin-oidc/",
PostLogoutRedirectUri = baseClientAddress + "signout-callback-oidc/",
ResponseType = "code id_token",
Scope = "openid api1 ratingspro.webapi offline_access",
UseTokenLifetime = false,
SignInAsAuthenticationType = DefaultAuthenticationType,
RequireHttpsMetadata = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
var client = HttpClientFactory.Create();
var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest
{
Address = IdsvrConstants.TokenEndpoint,
ClientId = "ratingspro.web",
ClientSecret = "secret",
Code = n.Code,
RedirectUri = n.RedirectUri,
});
if (tokenResponse.IsError)
{
LogHelper.LogMessage("RatingsproApp: Startup => tokenResponseError: " + tokenResponse.Error);
throw new AuthenticationException(tokenResponse.Error);
}
var userInfoResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = IdsvrConstants.UserInfoEndpoint,
Token = tokenResponse.AccessToken
});
if (userInfoResponse.IsError)
{
throw new AuthenticationException(userInfoResponse.Error);
}
var claims = userInfoResponse.Claims;
if (claims.Any(c => c.Type == "ApplicationAccessDenied"))
{
throw new AuthenticationException(claims.FirstOrDefault(c => c.Type == "ApplicationAccessDenied").Value);
}
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(claims);
id.AddClaim(new Claim("AccessToken", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString(CultureInfo.InvariantCulture)));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
client.Dispose();
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
n.ProtocolMessage.IdTokenHint = idTokenHint;
}
return Task.FromResult(0);
},
AuthenticationFailed = x =>
{
x.ProtocolMessage.RedirectUri = "/";
return Task.CompletedTask;
}
}
});
}
}

asp.net core api Redirect Unauthorizes request to another url [duplicate]

I have this JWT authorization configuration in my Startup.cs:
services.AddAuthentication(opts =>
{
opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opts =>
{
opts.RequireHttpsMetadata = false;
opts.SaveToken = true;
opts.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my_secret_key")),
ValidIssuer = "iss",
ValidAudience = "aud",
ValidateIssuerSigningKey = true,
ValidateLifetime = true
};
});
My HomeController has [Authorize] attribute. So upon access to Home/Index, I get a 401 response and I am presented with a blank page. I want to redirect to my Account/LogIn page but I am not sure how to do it.
I read that this shouldn't automatically redirect because it won't make sense to API calls if they are not authorized and then you redirect them, so what is the proper way on how I would get them to the login page on 401.
Please bear in mind that in this project, I have both Web API and Action methods with [Authorize] attributes so I need to redirect only when it is an action method.
You may use StatusCodePages middleware. Add the following inot your Configure method:
app.UseStatusCodePages(async context => {
var request = context.HttpContext.Request;
var response = context.HttpContext.Response;
if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
// you may also check requests path to do this only for specific methods
// && request.Path.Value.StartsWith("/specificPath")
{
response.Redirect("/account/login")
}
});
I read that this shouldn't automatically redirect because it won't make sense to API calls
this relates to API calls, that returns data other than pages. Let's say your app do call to API in the background. Redirect action to login page doesn't help, as app doesn't know how to authenticate itself in background without user involving.
Thanks for your suggestion... after spending a good time on google i could find your post and that worked for me. You raised a very good point because it does not make sense for app API calls.
However, I have a situation where the Actions called from the app has a specific notation route (/api/[Controller]/[Action]) which makes me possible to distinguish if my controller has been called by Browser or App.
app.UseStatusCodePages(async context =>
{
var request = context.HttpContext.Request;
var response = context.HttpContext.Response;
var path = request.Path.Value ?? "";
if (response.StatusCode == (int)HttpStatusCode.Unauthorized && path.StartsWith("/api", StringComparison.InvariantCultureIgnoreCase))
{
response.Redirect("~/Account/Login");
}
});
This works for both Razor Pages and MVC Views as follows: response.Redirect("/Login"); for Razor Pages. response.Redirect("/Home/Login"); for MVC Views. In order for this to work, the Authorize filter has to be added to the Controller. The code block also has to be added between app.UseAuthentication(); and app.UseAuthorization(); methods.
app.UseStatusCodePages(async context =>
{
var response = context.HttpContext.Response;
if (response.StatusCode == (int)HttpStatusCode.Unauthorized ||
response.StatusCode == (int)HttpStatusCode.Forbidden)
response.Redirect("/Home/Login");
});

AddOAuth is not defining the Callbackpath specified (getting a 404 on it) - ASP .NET Core 3

I am trying to implement an OAuth2 client using ASP.NET Core 3 application. Here is how I add OAuth to my startup
services.AddAuthentication(config =>
{
config.DefaultAuthenticateScheme = "Client.Auth.Cookie";
config.DefaultSignInScheme = "Client.Auth.Cookie";
config.DefaultChallengeScheme = "SelfServer";
})
.AddCookie("Client.Auth.Cookie")
.AddOAuth("SelfServer", config =>
{
config.CallbackPath = "/oauth/callback";
config.AuthorizationEndpoint = "https://Server/oauth/authorize";
config.TokenEndpoint = "https://Server/oauth/token";
config.ClientId = "clientid";
config.ClientSecret = "secret_key";
});
As I read in the documentation, the /oauth/callback is something I do not have to define myself (no need to create OAuthController with Callback action). I kind of by mistake did it and defined it myself, then when I realized, I deleted the OAuthController and now I am getting a 404 on https://client/oauth/callback.
What am I missing?
Alright I realized a few seconds after posting this question that I forgot to call
app.UseAuthentication()
in my Configure() method in the Startup.

ITfoxtec.Identity.Saml2 - Multiple authentication schemes with Asp.net Core Identity

I am aware of the answer given here which is about using Forms Authentication & SAML. In My case I am using Asp.net core Identity on .Net 5. Also I am using two authentication schemes (Cookies & JWT).
My auth pipeline goes as;
//include identity
services.AddIdentity<ApplicationUser, ApplicationRole>(SetupIdentityOptions)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//configure cookie and Jwt scheme
services.ConfigureApplicationCookie(...)
services.AddAuthentication(...) //configures default Identity
.AddJwtBearer(options => {...})
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
What I want to know is where should I add SAML2 in this pipeline.
In general the application should;
Be able to login with Cookie authentication (Identity takes care of this)
Jwt tokens should work as well for Apis (this also works with Jwt scheme)
SSO clients should get authenticated from their IdP and when redirected to AssertionConsumer() I will create additional claims create new ClaimsIdentity, create a JWT token(pass it to client) and get the user to dashboard.
I am stuck at 3rd point which is to properly add the SAML scheme without overriding cookie/jwt schemes.
The error No sign-in authentication handler is registered for the scheme 'saml2' probably occurs bedause you need to add services.AddSaml2() and app.UseSaml2()
You can use the setup from the provided example project. Newly added middleware should not interfere with what you already have.
When a SAML2 IdP redirects back to your application, you are given a result that identifies the authenticated user, e.g. Email Address or SSN (in case it is a government Id Provider).
You can combine that information with a Role (e.g. SpecialCustomer, Citizen, or an existing Role that you already have) into a cookie or JWT Token as you probably already do for other users. This can always be distinguished from other cookies and tokens by the Role. Regular [Authorize(....)] attributes will also work just fine.
I was stuck at the same point.. The solution I found:
If you check the source code of IFOXTEC.IDENTITY.SAML2, the method AddSaml2 overrides your AddAuthentication method and adds the AddCookie section.
public static IServiceCollection AddSaml2(this IServiceCollection services, string loginPath = "/Auth/Login", bool slidingExpiration = false, string accessDeniedPath = null, ITicketStore sessionStore = null, SameSiteMode cookieSameSite = SameSiteMode.Lax, string cookieDomain = null, CookieSecurePolicy cookieSecurePolicy = CookieSecurePolicy.SameAsRequest)
{
services.AddAuthentication(Saml2Constants.AuthenticationScheme)
.AddCookie(Saml2Constants.AuthenticationScheme, o =>
{
o.LoginPath = new PathString(loginPath);
o.SlidingExpiration = slidingExpiration;
if(!string.IsNullOrEmpty(accessDeniedPath))
{
o.AccessDeniedPath = new PathString(accessDeniedPath);
}
if (sessionStore != null)
{
o.SessionStore = sessionStore;
}
o.Cookie.SameSite = cookieSameSite;
o.Cookie.SecurePolicy = cookieSecurePolicy;
if (!string.IsNullOrEmpty(cookieDomain))
{
o.Cookie.Domain = cookieDomain;
}
});
return services;
}
So, to add SAML to your pipeline, you can remove the services.AddSaml2(), add the AddCookie section and, inside your policy, you can add the verification of any cookie with name saml2 to forward to your SAML schema.
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = "custom-schema";
sharedOptions.DefaultChallengeScheme = "custom-schema";
})
.AddPolicyScheme("custom-schema", null, options =>
{
options.ForwardDefaultSelector = context =>
{
if (context.Request.Headers["Authorization"].Any(x => x.StartsWith("Bearer ")))
return JwtBearerDefaults.AuthenticationScheme;
else if (context.Request.Headers["Cookie"].Any(x => x.Contains(".saml2=")))
return Saml2Constants.AuthenticationScheme;
return "Identity.Application";
};
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, null, options =>
{
//...
})
.AddCookie(Saml2Constants.AuthenticationScheme, o =>
{
o.LoginPath = new PathString("/Auth/Login");
o.SlidingExpiration = false;
o.Cookie.SameSite = SameSiteMode.Lax;
o.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});

How to limit an aspnet core Action for specific Roles with Auth0 and JWT

I'm creating a PWA with React and using Auth0 as my identity provider and JWT Bearer token as authentication. I am injecting roles into my JWT token so that the client-side can limit what options are available to the user and I have this working pretty well.
I want to now limit the server side so that an endpoint can't be called unless the user has the necessary role(s) required to access that endpoint.
Annoyingly, Auth0 doesn't appear to support adding in the roles or role claim that aspnet core seems to handle OOTB; it requires that a domain preface the roles in the claims definition. ie, https://bob.com/roles as the claim.
I'm trying to work out how to get the Authorize(Roles = "Administrator") attribute to honour the domain-prefaced claim for roles.
I have tried updating the Auth0 Rule to set the role or roles property but these never get returned; only the domain-prefaced roles claim seems to return.
I have found other info for more specific Authentication providers and they include a MapJsonKey extension on ClaimActions that look like they would fit the bill, but the standard AuthenticationOptions object in the AddAuthentication extension doesn't appear to have this.
My ConfigureServices in App.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// 1. Add Authentication Services
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = Configuration["Auth0:Authority"];
options.Audience = Configuration["Auth0:ClientId"];
});
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
My Rule for injecting the roles into the JWT in Auth0:
function (user, context, callback) {
const namespace = 'http://bob.com';
const assignedRoles = (context.authorization || {}).roles;
let idTokenClaims = context.idToken || {};
let accessTokenClaims = context.accessToken || {};
idTokenClaims[`roles`] = assignedRoles; // This was an attempt to set the roles in 'roles' but doesn't get returned.
accessTokenClaims[`roles`] = assignedRoles;
idTokenClaims[`${namespace}/roles`] = assignedRoles; // This does get returned
accessTokenClaims[`${namespace}/roles`] = assignedRoles;
context.idToken = idTokenClaims;
context.accessToken = accessTokenClaims;
callback(null, user, context);
}
Example JWT Payload
{
"http://bob.com/roles": [
"Administrator"
],
"given_name": "Name",
"iss": "{issuer}",
"sub": "{subject}",
"aud": "{audience}"
}
asp.net core Action (taken from the example project, but with auth added)
[HttpGet("[action]"), Authorize(Roles = "Administrator")]
public IEnumerable<WeatherForecast> WeatherForecasts()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
DateFormatted = DateTime.Now.AddDays(index).ToString("d"),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
});
}
What I would like is to be able to either map the http://bob.com/roles to roles, get the aspnet core Authorize attribute to look at the http://bob.com/roles, or get Auth0 to be able to return the roles in a 'roles' object.
Where I got the MapJsonKey info from:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-2.2
Using Roles with the ASP.NET Core JWT middleware
https://www.jerriepelser.com/blog/using-roles-with-the-jwt-middleware/
For anyone who finds this, I found a solution to this. If you update the JWT claim to be http://schemas.microsoft.com/ws/2008/06/identity/claims/role then it all works straight away.
Updated Auth0 Rule
function (user, context, callback) {
const assignedRoles = (context.authorization || {}).roles;
let idTokenClaims = context.idToken || {};
let accessTokenClaims = context.accessToken || {};
idTokenClaims[`http://schemas.microsoft.com/ws/2008/06/identity/claims/role`] = assignedRoles;
accessTokenClaims[`http://schemas.microsoft.com/ws/2008/06/identity/claims/role`] = assignedRoles;
context.idToken = idTokenClaims;
context.accessToken = accessTokenClaims;
callback(null, user, context);
}