Closed HaoK closed 7 years ago
@AndyCreigh
You should change the Cookie.SecurePolicy
:
https://github.com/aspnet/Identity/issues/1364#issuecomment-323321248
@rbasniak you cut out a bunch of your code, the logs claim you have identity running. Show more of the code.
@PaulAngelino
// should be added after the AddIdentity
services.ConfigureApplicationCookie(identityOptionsCookies =>
{
identityOptionsCookies.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
identityOptionsCookies.LoginPath = "/Account/Login";
identityOptionsCookies.LogoutPath = "...";
});
@VahidN I have that same problem too.your code used cookie but i use jwt token. In .NET Core 1 i used following code:
app.UseIdentity();
app.UseCookieAuthentication(options:new CookieAuthenticationOptions()
{
AutomaticChallenge = false
});
And it worked fine.but in .NET Core 2.0 i could not found this property.
@shakeri It's documented here https://github.com/aspnet/Announcements/issues/232
@davidfowl I edited the original question to show the entire code of my Configure
and ConfigureServices
methods.
@VahidN Thanks for the suggestion, didn't work for me. I tried both options below, login is still redirecting to the same page when I have [Authorize] on the controller.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = new PathString("/Account/Login/");
options.LogoutPath = new PathString("/Account/Logoff/");
options.AccessDeniedPath = new PathString("/Account/AccessDenied/");
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
services.ConfigureApplicationCookie(identityOptionsCookies =>
{
identityOptionsCookies.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});.
@rbasniak calling AddIndentity changes the default authentication scheme. That's why cookies is being used instead of jwt.
@rbasniak
When you use jwt token authentication you don't use AddIndentity
.
In my Startup
Class is the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
....
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("PolicyName", policy =>
{
policy.RequireRole("Roles");
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
});
});
services.AddMvc(});
services.AddScoped<IUserManager, UserManager>();
services.AddScoped<UserManager<User>, UserManager>();
services.AddScoped<IRoleManager, RoleManager>();
services.AddScoped<RoleManager<Role>, RoleManager>();
services.AddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
services.AddScoped<ISignInManager, SignInManager>();
services.AddScoped<SignInManager<User>, SignInManager>();
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvc();
}
And TokenController
is the following:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Post([FromForm]TokenViewModel model)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null) return BadRequest();
if (!await _userManager.CheckPasswordAsync(user, model.Password)) return Unauthorized();
var principal = await _signInManager.CreateUserPrincipalAsync(user);
var token = _jwtService.GenerateClaimsIdentity(user,principal.Claims);
var response = new
{
Displayname = user.Displayname,
Username = user.Username,
access_token = await _jwtService.GenerateEncodedToken(token.Claims),
token_type = "bearer",
expires_in = TotalSeconds
};
return new OkObjectResult(response);
}
I never used AddIndentity
.
@shakeri I was addressing @rbasniak
@rheid var identityToken = await HttpContext.GetTokenAsync("id_token");
looks correct, it's possible something else is failing or has changed. E.g. if you don't provide a scheme for GetTokenAsync then it uses the DefaultAuthenticateScheme or DefaultScheme. Are those configured differently in your new app?
@davidfowl @shakeri Thank you for the help, that solved the problem. Now my authenticated controllers are working again. The problem is now on the ClaimsTransformer
. Before 2.0 it was working, now everytime the code hits the transformer the IsAuthenticathed
property is false
. Here's the code converted to 2.0:
public class ClaimsTransformer : IClaimsTransformation
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IHttpContextAccessor _httpContext;
public ClaimsTransformer(UserManager<ApplicationUser> userManager, IHttpContextAccessor httpAccessor)
{
_userManager = userManager;
_httpContext = httpAccessor;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (_httpContext.HttpContext.User.Identity.IsAuthenticated)
{
...
}
return principal;
}
}
In 1.1 I added the transformer like this in my Configure()
method:
app.UseClaimsTransformation(o => new ClaimsTransformer(userManager).TransformAsync(o));
Now in 2.0 as fas as I understood, it needs to be added as a service, so I'm doing like this:
services.AddSingleton<IClaimsTransformation, ClaimsTransformer>();
When I make a new request if I put a breakpoint in the ClaimsTransformer
it this before getting into the controller, but here the user is not authenticated. After it hits the controller the user is authenticated correctly.
Is this correct?
@VahidN, are you saying that I should use services.ConfigureApplicationCookie
instead of services.AddAuthentication().AddCookie(<CookieAuthenticationOptions>)
?
Can anyone help me to migrate to following code (1.1) to asp.net core 2.0 please
private void ConfigureAuth(IApplicationBuilder app)
{
SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_secretKey));
app.UseSimpleTokenProvider(new TokenProviderOptions
{
Path = "/api/Token",
Audience = "Mobile",
Issuer = "Issuer",
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.XXXX),
IdentityResolver = this.GetIdentity
});
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = "Issuer",
ValidateAudience = true,
ValidAudience = "Mobile",
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
}
private Task<ClaimsIdentity> GetIdentity(string username, string password)
{
if (username == "XXXXXX" && password == "XXXXXX")
{
return Task.FromResult(new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] { }));
}
return Task.FromResult<ClaimsIdentity>(null);
}
Thans a lot, I try this without success:
private void ConfigureAuth(IApplicationBuilder app)
{
SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_secretKey));
app.UseSimpleTokenProvider(new TokenProviderOptions
{
Path = "/api/Token",
Audience = "Mobile",
Issuer = "Issuer",
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.XXXX),
IdentityResolver = this.GetIdentity
});
}
private void ConfigureAuth(IServiceCollection services)
{
SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_secretKey));
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = "Issuer",
ValidateAudience = true,
ValidAudience = "Mobile",
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.Authority = "http://localhost:30940/"; // What I have to put here ??
options.Audience = "resource-server"; // What I have to put here ??
options.TokenValidationParameters = tokenValidationParameters;
});
}
private Task<ClaimsIdentity> GetIdentity(string username, string password)
{
if (username == "XXXXXXX" && password == "XXXXXXXXX")
{
return Task.FromResult(new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] { }));
}
return Task.FromResult<ClaimsIdentity>(null);
}
EDIT
Found the problem:
I've to add
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
And not just
[Authorize]
In the controller
Thanks a lot
Anyone adding multiple schemes? In aspnet core 1xx
i was able to add multiple cookie schemes. but after 2.0
i have no idea where to add these configs?
For example
services.AddAuthentication("myscheme1").AddCookie(o =>{
o.ExpireTimeSpan = TimeSpan.FromHours(1);
o.LoginPath = new PathString("/forUser");
o.Cookie.Name = "token1";
o.SlidingExpiration = true;
});
services.AddAuthentication("myscheme2").AddCookie(o =>{
o.ExpireTimeSpan = TimeSpan.FromHours(1);
o.LoginPath = new PathString("/forAdmin");
o.Cookie.Name = "token2";
o.SlidingExpiration = true;
});
and later in controller
// user controller
await context.HttpContext.ChallengeAsync("myscheme1");
// admin controller
await context.HttpContext.ChallengeAsync("myscheme2");
@irowbin The argument to AddAuthentication is setting the default scheme not adding a new one. This is what you want:
services.AddAuthentication()
.AddCookie("myscheme1", o =>
{
o.ExpireTimeSpan = TimeSpan.FromHours(1);
o.LoginPath = new PathString("/forUser");
o.Cookie.Name = "token1";
o.SlidingExpiration = true;
})
.AddCookie("myscheme2", o =>
{
o.ExpireTimeSpan = TimeSpan.FromHours(1);
o.LoginPath = new PathString("/forAdmin");
o.Cookie.Name = "token2";
o.SlidingExpiration = true;
});
@davidfowl Thanks. Its working. π
hi @davidfowl My protected page keep redirecting to login page. I've followed instructions from above comments but no luck. I can see that the cookie is return in browser application tab but when i try to access claims from HttpContext.User.Claims
, there is no results and redirected to login. What i am missing?
here is my code.
// startup.cs
services.AddAuthentication().AddCookie("scheme1", o =>
{
o.ExpireTimeSpan = TimeSpan.FromHours(1);
o.LoginPath = new PathString("/account");
o.Cookie.Name = "user_token";
o.SlidingExpiration = true;
});
// Configure method
app.UseAuthentication();
// account controller signin method
var claims = new List<Claim>
{
new Claim( UniqueName, UserName, ClaimValueTypes.String)
};
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync("scheme1", principal);
After login success, i can see the cookie is return to the browser and also i can get cookies in context.HttpContext.Request.Cookies;
but there is no claims in context.HttpContext.User.Claims
.
in aspnet core 1x
i can access claims by doing this but not now. Thanks.
In 1.0, I was using middleware branching so I could support both basic and bearer authentication (Needed this while apps were migrated). I detected whether the basic or bearer authentication header was available and ran the relevant middleware like so:
application.UseIfElse(
x => x.Request.Headers.HasBasicAuthenticationHeader(),
x => x.UseBasicAuthentication(options => ...),
x => x.UseJwtBearerAuthentication(options => ...))
In 2.0, there is only one middleware being run and I have to configure basic and bearer authentication for it. Is there a way I can add some branching logic instead of setting a default authentication scheme?
Hi , I've just migrated to .net core 2 and I see lot's of changes in Authentication setup, all good but I can't figure out how to migrate this problem : Back in the .Net Core 1 If we wanted to set a different pipeline for some requests we could use IApplicationBuilder Map Extension Method this way
app.Map("/api", apiApp =>
{
apiApp.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "https://localhost:44380",
AutomaticAuthenticate = true,
ApiName = "AuthAPI"
});
apiApp.UseMvc();
});
But now everything is gone up to the ConfigureServices Method, and I can't figure out how to migrate this part of my code! please help!
@irowbin looks like it should work. Can you make a minimal repro project using ASP.NET Core 2.0 that reproduces the problem in a github repository?
It turns out a massive amount of people are not using the AuthenticationSchemes
property on the AuthorizeAttribute
and instead are using middleware branching to select the current scheme for a request based on some logic (most commonly a path prefix /api
).
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("/api/products")]
public class ProductsController : Controller
{
[HttpGet]
public List<Product> Get() => ...;
}
This will use the correct authentication scheme when doing auth for this specific controller.
As an alternative, here's a middleware that can be used to change the current user based on the authentication scheme.
public class AuthenticateSchemeMiddleware
{
private readonly RequestDelegate _next;
private readonly string _scheme;
public AuthenticateSchemeMiddleware(RequestDelegate next, string scheme)
{
_next = next;
_scheme = scheme ?? throw new ArgumentNullException(nameof(scheme));
}
public async Task Invoke(HttpContext httpContext)
{
var result = await httpContext.AuthenticateAsync(_scheme);
if (result.Succeeded)
{
httpContext.User = result.Principal;
}
await _next(httpContext);
}
}
public static class AuthenticateMiddlewareExtensions
{
public static IApplicationBuilder UseAuthenticationScheme(this IApplicationBuilder builder, string scheme)
{
return builder.UseMiddleware<AuthenticateSchemeMiddleware>(scheme);
}
}
Usage looks like this:
In Startup.ConfigureServices
void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddJwtBearer(...);
}
In Startup.Configure
app.Map("/api", sub => sub.UseAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme));
[Authorize]
[Route("/api/products")]
public class ProductsController : Controller
{
[HttpGet]
public List<Product> Get() => ...;
}
UPDATE: I should point out that if you're using this middleware and the user isn't authorized, the challenge/forbid issued will end up being the default scheme. If none is set, an exception will be thrown.
@davidfowl I've added a repo. Sorry for the boilerplate stuffs. π The startup.cs and the HomeController.cs.
Just run the app and index
will create a cookie.
Navigate to the about
and debug the HttpContext.User
The claims is empty.
@davidfowl Thank you so much! I just needed that Authentication for my Api Controllers and I thought using AuthenticationScheme and adding that Authentication MiddleWare may have impact on the performance of Paths that don't start with "api" , isn't this true?
Anyway, I just followed tutorials on the Internet , sorry for not using AuthenticationScheme on AuthorizeAttribute π
@rbasniak Can you show your change code in Startup.cs ?
Is there any built-in solution to use Claims Transformation when using Windows Authentication on IIS or do I have to create ClaimsTransformerMiddleware myself?
@kiss96803 My entire Configure
and ConfigureServices
are posted a few comments above, because I was having another problem converting my authentication code do 2.0. Here's the link to my code: https://github.com/aspnet/Security/issues/1310#issuecomment-323560279
@davidfowl After some struggle i am able to get claims by doing like this.
// login method
public async Task<IActionResult> Login()
{
var claims = new List<Claim>
{
new Claim( UniqueName, UserName, ClaimValueTypes.String)
};
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity,"scheme1"); // notice authentication type. if i remove it, won't work.
await HttpContext.SignInAsync("scheme1", principal);
return Ok();
}
// only works if i decorate my controller like this
[Authorize(AuthenticationSchemes ="scheme1")] // if i remove it, claims are empty.
public class HomeController : Controller
{
public IActionResult Index()
{
var claims = HttpContext.User.Claims; // claims are available.
return Ok();
}
}
In aspnet core 1x
i don't have to add that decorator to the controller and the authentication type to the ClaimsIdentity
to get claims but now it won't work without it. The aspnet core
docs is really poor with minimal details.
This is how it works for me currently, maybe this will help someone with custom roles. In the controller code you can specify the roles dynamically then in the controller you can specify the roles in the authorization annotation e.g. [AllowAnonymous] or [Authorize(CustomRoles.User)], etc. I have a class called CustomRoles (sudo Enum basically) but that can easily be just string values in the example where used.
ConfigureServices:
// Only needed for custom roles.
services.AddAuthorization(options =>
{
options.AddPolicy(CustomRoles.Administrator, policy => policy.RequireRole(CustomRoles.Administrator));
options.AddPolicy(CustomRoles.User, policy => policy.RequireRole(CustomRoles.User));
options.AddPolicy("UserId", policy => policy.RequireClaim("UserId"));
options.AddPolicy("Email", policy => policy.RequireClaim("Email"));
});
// Needed for cookie auth.
services
.AddAuthentication(o =>
{
o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/Authorization/Login";
options.LogoutPath = "/Authorization/Logout";
options.AccessDeniedPath = new PathString("/Home/Forbidden/");
options.Cookie.Name = "cookie";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.SlidingExpiration = true;
});
Configure:
app.UseAuthentication();
Controller:
// Create cookie for this site.
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
new Claim(ClaimTypes.Role, CustomRoles.Administrator)
};
var claimsIdentity = new ClaimsIdentity(claims, "Login");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
// Sign in with specific auth properties.
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
claimsPrincipal,
new AuthenticationProperties
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(_settings.LoginCookieExpirationMinutes),
AllowRefresh = false,
IsPersistent = false
}
);
Since this is an older closed issue, please use https://github.com/aspnet/Security/issues/1338 so we have one consolidated issue
@rbasniak why are you accessing HttpContextAccessor.HttpContext.User in your transformation rather than operating on the ClaimsPrincipal passed to you as an argument?
Transformations are run prior to assigning HttpContext.User:
@irowbin if you specify services.AddAuthentication("scheme1")
then that's the scheme that will be used for the default [Authorize]
, otherwise you have to specify it on each authorize attribute.
@RehanSaeed UseJwtBearer already no-ops if the header doesn't contain Bearer, and I assume UseBasicAuth would do something similar. The far more interesting question I would expect is how to specify which to issue a challenge for.
@poplawsm implement a transformer: https://github.com/aspnet/HttpSysServer/issues/389
@Tratcher I'm thinking I could override the AuthenticationSchemeProvider
's GetDefaultAuthenticateSchemeAsync() method with my own implementation that selects the authentication scheme based on the HTTP headers in the request, instead of using the DefaultAuthenticateScheme
. Alternatively I can also override this behaviour in the AuthenticationMiddleware itself. Would that be sufficient for my needs?
@RehanSaeed Sure, try it. However, how do you decide which handler should handle the challenge for unauthenticated requests?
@AndyCreigh are you mixing http and https? Chrome doesn't like this. https://github.com/aspnet/Mvc/issues/6673
@Tratcher Thank you, the parameter type changed and I didn't notive that it has the authenticaded user data. Now the whole authentication system is back...
I'm using method GetAuthenticationSchemes
to get authentification schemes:
var schemes = _httpContextAccessor.HttpContext.Authentication.GetAuthenticationSchemes();
But after migrating project to .Net Core 2.0 every build I see that 'HttpContext.Authentication' is obsolete
. Is it possible to get authentification schemes with?
Use IAuthenticationSchemeProvider.GetAllSchemesAsync
to get them all
You'll have to get the service from DI/request services, its not hanging off of context directly anymore
This thread is complex, can you please provide a simple JWT Token Bearer example for Core 2 please?
@coderabsolute
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["JWT:SignKey"])),
ValidateAudience = true,
ValidAudience = Configuration["JWT:ValidIssuer"],
ValidateIssuer = true,
ValidIssuer = Configuration["JWT:ValidIssuer"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
@Tratcher Always redirecting to scheme1
why?
services.AddAuthentication()
.AddCookie("scheme1", o =>
{
o.ExpireTimeSpan = TimeSpan.FromHours(1);
o.LoginPath = new PathString("/forUser");
o.Cookie.Name = "token1";
o.SlidingExpiration = true;
})
.AddCookie("scheme2", o =>
{
o.ExpireTimeSpan = TimeSpan.FromHours(1);
o.LoginPath = new PathString("/forAdmin");
o.Cookie.Name = "token2";
o.SlidingExpiration = true;
});
[Authorize(AuthenticationSchemes ="scheme1,scheme2")]
public class HomeController : Controller
{
public IActionResult Index()
{
return Ok("cool");
}
}
@irowbin What do you expect it to do?
Hi @davidfowl I am filtering claims from method.
Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next )
I want to invoke this method but the authentication keep redirecting me to the login path without executing this method? In 1x
i can get claims without that authorize
decorator, now the things are changed. I have multiple login pages based on that schemes so i am redirecting manually by getting schemes from method like this
[ClaimRequirement("home", "read", "scheme1")]
public IActionResult Index1()
{
return View();
}
[ClaimRequirement("home", "read", "scheme2")]
public IActionResult Index2()
{
return View();
}
public class ClaimRequirementFilter : IAsyncActionFilter
{
private readonly string _scheme;
public ClaimRequirementFilter (string scheme)
{
_scheme = scheme;
}
Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next )
{
await context.HttpContext.ChallengeAsync( _scheme );
}
}
In 1x i can get claims without that authorize decorator, now the things are change
You just want to get the user for the appropriate scheme?
var result = await context.AuthenticateAsync(scheme);
How were you getting at the right user before?
@irowbin I think I know what you were doing before (through the series of posts), please correct me if I'm wrong. You had 2 cookie middleware in the pipeline with automatic authenticate set to true. When the request came in, you had potentially multiple identities on the claims principal and your custom action filter would get the user for the appropriate scheme (I'd like to see that code) and check the claims against them.
Is that right?
YES that's right. I've already migrate to 2.0
.
this is how i try to get claims
Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next )
{
var anyClaims = context.HttpContext.User.Claims.Any( c => c.Type == JwtRegisteredClaimNames.UniqueName );
if(!anyClaims )
await context.HttpContext.ChallengeAsync( _scheme );
esle next();
}
If i remove that authorize
attribute then there is no claims and users but if i add that, the users are available but then i have to explicit the schemes too.
If i remove that authorize attribute then there is no claims and users but if i add that, the users are available but then i have to explicit the schemes too.
Without writing manual code there's no way to merge all the identities into the single claims principal. There's a helper but I'd recommend against it.
What you want to do doesn't require a filter at all, is there any reason you wrote one instead of using an authorization policy?
From this attribute - [ClaimRequirement("home", "read", "scheme1")]
What does "home" and "read" map to?
For completeness though, the code you would write looks like this:
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var result = await context.HttpContext.AuthenticateAsync(_scheme);
if (result.Succeeded)
{
var anyClaims = result.Principal.Claims.Any(c => c.Type == JwtRegisteredClaimNames.UniqueName);
if (!anyClaims)
{
await context.HttpContext.ChallengeAsync(_scheme);
return;
}
}
await next();
}
@davidfowl My custom action filter is to look for the permission from database. create, read, update, delete
so i have to invoke custom action filter.
[ClaimRequirement(
"home", // the protected resource. Such as the controller name.
"read", // readonly permission
"scheme1")] // authentication type
public async Task<IActionResult> Index()
{
return View();
}
Note: this issue is closed, you should use https://github.com/aspnet/Security/issues/1338
Summary:
The old 1.0 Authentication stack no longer will work, and is obsolete in 2.0. All authentication related functionality must be migrated to the 2.0 stack, any interop between old and new must be side by side apps, as opposed to mixing 1.0 auth code with 2.0 auth code in the same app. Cookie authentication will interop, so 1.0 Cookies and 2.0 Cookies will be valid in both apps if configured properly. The main motivation was to move to a more flexible service based
IAuthenticationService
and away from the old middleware/IAuthenticationManager
design that came over from Microsoft.Owin.IAuthenticationManager
(akahttpContext.Authentication
) is now obsoleteThis was the main entry point into the old auth system. This has now been replaced with a new set of
HttpContext
extensions that live in theMicrosoft.AspNetCore.Authentication
namespace and remain very similar:Configure():
UseXyzAuthentication
has been replaced by ConfigureService(): AddXyz()In Auth 1.0, every auth scheme had its own middleware, and startup looked something like this:
In Auth 2.0, there is now only a single Authentication middleware, and each authentication scheme is registered during
ConfigureServices
, andUseIdentity()
is no longer required (since it was just calling UseCookie 4 times underneath the covers)New
Microsoft.AspNetCore.Authentication.Core/Abstractions
All of the old Authentication namespaces in HttpAbstractions have been deprecated. The new Auth 2.0 stack lives in two new packages inside the HttpAbstractions repo:
Microsoft.AspNetCore.Authentication.Core/Abstractions
.Brief overview:
IAuthenticationService
: used by theHttpContext
extension methods to expose the 5 main operationsAuthenticate/Challenge/Forbid/SignIn/SignOut
IAuthenticationHandler
: Defines the required operations for all handlers: Authenticate/Challenge/ForbidIAuthenticationSignIn/OutHandler
: Implemented to add the SignIn/SignOut methods respectivelyIAuthenticationRequestHandler
: Implemented by handlers that need to participate in request handler, i.e. remote authentication schemes like OAuth/OIDC that need to process 3rd party auth responses.AuthenticationScheme
: represents a logical named authentication scheme to target for any givenIAuthenticationService
method, it binds the scheme name (and optional display name) to anIAuthenticationHandler
which implements the scheme specific logic.IAuthenticationSchemeProvider
: responsible for managing which schemes are supported, and what the defaults are for each operation (the default implementation just reads from theAuthenticationOptions
)IAuthenticationHandlerProvider
: responsible for returning the correct handler instance for a given scheme and request.IAuthenticationFeature
: used to capture the original request path/pathbase so redirects can be computed property after anapp.Map()
Types that are mostly unchanged, just with new homes:
AuthenticationProperties
: metadata for authentication operations.AuthenticationTicket
: used to store a claims principal (user) + authentication propertiesAuthenticateResult
: return value forAuthenticateAsync
, contains either a ticket, or a failureSecurity repo:
Microsoft.AspNetCore.Authentication
/ AuthenticationHandler changesAll of the core abstractions and services for authentication live in HttpAbstracions, but there's an additional layer of base classes/functionality targeted towards implementation of AuthenticationHandlers. This is also where the AuthenticationMiddleware lives. The handlers themselves for the various implementations aren't drastically different, but there were a fair amount of changes
Microsoft.AspNetCore.Authentication
Overview:
AuthenticationMiddleware
:UseAuthentication()
adds this middleware which does two things. By default It will automatically Authenticate usingAuthenticationOptions.DefaultAuthenticateScheme
to sethttpContext.User
if specified. It also will giveIAuthenticationRequestHandler's
a chance to handle the request.AuthenticationSchemeOptions
: Base class for options used with theAuthenticationHandler
base class, it defines two common properties in Events and ClaimsIssuer, as well as a virtualValidate()
method that will be called on every request by the handler.AuthenticationHandler<TOptions>
: Abstract base class for handlers who must implementHandleAuthenticateAsync
. The rest ofIAuthenticationHandler
is implemented with some reasonable defaults, Challenge(401)/Forbid(403), and logic to handle per request initialization usingIOptionsMonitor<TOptions>.Get(authenticationScheme.Name)
to resolve the handler's options.RemoteAuthenticationHandler<TOptions>
: Adds the abstractHandleRemoteAuthenticateAsync
and implementsHandleAuthenticateAsync
to call this method. This is meant to be used for 3rd party authentication, i.e. OAuth/OIDC. It adds an additional constraint to TOptions that requires them to beRemoteAuthenticationOptions
which adds a bunch of settings like CallbackPath, CorrelationCookie, Backchannel which are needed to talk to a remote authentication provider.AuthenticationBuilder
: is a new class which is used to group all of the AddXyz() authentication methods. This is returned byservices.AddAuthentication()
and is where specific authentication methods are expected to add themselves as extension methods, i.e. AddCookie(), AddGoogle(), AddFacebook().Event changes overview
At a high level, there 3 main kinds of events:
BaseContext
events which are the simplest and just expose properties with no real control flow.ResultContext
events which revolve around producingAuthenticateResult
s which expose:Success()
: used to indicate that authentication was successful and to use the Principal/Properties in the event to construct the result.NoResult()
: used to indicate no authentication result is to be returned.Fail()
: used to return a failure.HandleRequestContext
events are used in the IAuthenticationRequestHandler/HandleRemoteAuthenticate methods and adds two more methods:HandleResponse()
: used to indicate that the response was generated and the AuthenticationMiddleware should not invoke the rest of the middlewares in the pipeline after it.SkipHandler()
used to indicate that this handler is done with the request, but subsequent handlers will be called, as well as any other middleware in the pipeline if none of those handlers handle the request.AutomaticAuthentication/Challenge
have been replaced byDefault[Authenticate/Challenge]Scheme
AutomaticAuthentication/Challenge
were intended to only be set on one authentication scheme, but there was no good way to enforce this in 1.0. These have been removed as flags on the individual AuthenticationOptions, and have been moved into the base AuthenticationOptions which can be configured in the call toAddAuthentication(authenticationOptions => authenticationOptions.DefaultScheme = "Cookies")
.There are now overloads that use the default schemes for each method in
IAuthenticationService
DefaultScheme
: if specified, all the other defaults will fallback to this valueDefaultAuthenticateScheme
: if specified,AuthenticateAsync()
will use this scheme, and also theAuthenticationMiddleware
added byUseAuthentication()
will use this scheme to setcontext.User
automatically. (Corresponds to AutomaticAuthentication)DefaultChallengeScheme
if specified,ChallengeAsync()
will use this scheme,[Authorize]
with policies that don't specify schemes will also use thisDefaultSignInScheme
is used bySignInAsync()
and also by all of the remote auth schemes like Google/Facebook/OIDC/OAuth, typically this would be set to a cookie.DefaultSignOutScheme
is used bySignOutAsync()
falls back toDefaultSignInScheme
DefaultForbidScheme
is used byForbidAsync()
, falls back toDefaultChallengeScheme
"Windows" Authentication(HttpSys/IISIntegration)
The host behavior hasn't changed too much, but now they each register a single "Windows" authentication scheme. Also IISIntegration now conditionally registers the handler only if windows auth is enabled in IIS (if you have the latest version of ANCM, otherwise it's always registered as before).
Authorization changes
IAuthorizationService.AuthorizeAsync
now returnsAuthorizationResult
instead of boolIn order to enable scenarios around authorization failures,
IAuthorizationService
now returns a result object which allows access to the reasons whyAuthorizeAsync
failed (eithercontext.Fail()
, or a list of failed requirements)Removal of
ChallengeBehavior
=> newPolicyEvaluator
In Auth 1.0, there was a
ChallengeBehavior
enum that was used to specify either Automatic/Unauthorized/Forbid behaviors to signal to the auth middleware what behavior the caller wanted. Automatic was the default and would go down the Forbid(403) code path if the middleware already had an authentication ticket, otherwise would result in Unauthorized(401).In Auth 2.0, this behavior has been moved into a new Authorization.Policy package, which introduces the
IPolicyEvaluator
which uses bothIAuthenticationService
(when requested via policy.AuthenticationSchemes), andIAuthorizationService
to decide whether to return a tri statePolicyAuthorizationResult
(Succeeded/Challenged/Forbidden).Overview of
[Authorize]
The
[Authorize]
attribute hasn't changed much, but the there were some implementation details that have changed significantly in MVC'sAuthorizeFilter
, and here's an overview of how things work: AuthorizeFilter source[Authorize]
attributes on the controller/method/globally.IPolicyEvaluator.AuthenticateAsync(policy, httpContext)
is called, by default, if the has specified anypolicy.AuthenticationSchemes
,AuthenticateAsync
will be called on each scheme, and each resultingClaimsPrincipal
will be merged together into a single ClaimsPrincipal set on context.User. If no schemes were specified, the evaluator will attempt to use context.User if it contains an authenticated user. This is usually the normal code path, asDefaultScheme/DefaultAuthenticateScheme
will be set to the main application cookie, and theAuthenticationMiddleware
will have already set context.User using this scheme'sAuthenticateAsync()
Authenticate logicAllowAnoynmous
was specified, authorization is skipped, and the filter logic short circuits and is done.IPolicyEvaluator.AuthenticateAsync(policy, authenticationResult, httpContext)
is called with the result from step 2. This just basically turns into a call toIAuthorizationService.AuthorizeAsync
, and the result is used to determine the appropriateChallenge/ForbidResult
if needed.Claims Transformation
Simpler, new
IClaimsTransformation
service with a single method:Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
We call this on any successful
AuthenticateAsync
call.Known issues/breaking changes: