ASP.NET Core & MVC 2Security Overview
Dominick Baier@leastprivilege
2@leastprivilege
Me
• Independent Consultant – Specializing on Application Security Architectures
– Working with Software Development Teams (ISVs and in-house)
• Creator and Maintainer of IdentityServer OSS Project– Certified OpenID Connect & OAuth 2.0 Implementation for .NET
– https://identityserver.io
email [email protected] http://leastprivilege.comtwitter @leastprivilegeslides https://speakerdeck.com/leastprivilege
Modern Application Architecture
Browser
Native App
Server App/Thing
Web App
Service Service
Service
Identity
4@leastprivilege
.NET Timeline
2002
.NET 1.0ASP.NET 1.0
2006
.NET 3.0WCF (#fail)
2009
WIF
2012
.NET 4.5
2013
Katana 2.0
2014
Katana 3.0
2016
.NET CoreASP.NET Core
v1
Claims+ Security Token Support= External Authentication
2017
.NET CoreASP.NET Core
v2
5@leastprivilege
The new .NET
6@leastprivilege
ASP.NET Core Architecture• ASP.NET Core is the HTTP runtime • MVC is Microsoft's primary application framework– combines web UI & API
Console Application
.NET (Core)
ASP.NET Core
Middleware MiddlewareUser Agent MVC
DI
7@leastprivilege
ASP.NET Core Security on a single Slide• Everything is based on ClaimsPrincipal
– no more custom IPrincipal
• It's all about services– authentication– authorization– claims transformation– data protection– anti-forgery– CORS– encoding
• Middleware for automatic per-request invocation
8@leastprivilege
Authentication in ASP.NET Core
• Handlers in DI implement specific authentication methods– Cookies for browser based authentication– Google, Facebook, and other social authentication*– OpenId Connect for external authentication– JSON web token (JWT) for token-based authentication
• Middleware invokes handlers for request related processing– handlers can be also invoked manually
* 20+ more https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
9@leastprivilege
Interacting with the authentication system
• Extension methods on HttpContext call the IAuthenticationService in DI
public static class AuthenticationHttpContextExtensions{
public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal) { }public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal) { }
public static Task SignOutAsync(this HttpContext context) { }public static Task SignOutAsync(this HttpContext context, string scheme) { }
public static Task ChallengeAsync(this HttpContext context) { }public static Task ChallengeAsync(this HttpContext context, string scheme) { }
public static Task ForbidAsync(this HttpContext context) { }public static Task ForbidAsync(this HttpContext context, string scheme) { }
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context) { }public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) { }
}
10@leastprivilege
Setting up authentication
• Global settings go into DI– e.g. default schemes
• Authentication middleware invokes handlers
public void ConfigureServices(IServiceCollection services){
services.AddAuthentication();}
public void Configure(IApplicationBuilder app){
app.UseAuthentication();}
11@leastprivilege
Cookie Authentication
public void ConfigureServices(IServiceCollection services){
services.AddAuthentication(defaultScheme: "Cookies").AddCookie("Cookies", options =>{
options.LoginPath = "/account/login";options.AccessDeniedPath = "/account/denied";
options.Cookie.Name = "myapp";options.Cookie.Expiration = TimeSpan.FromHours(8);options.SlidingExpiration = false;
});}
12@leastprivilege
Cookies: Logging in• SignInAsync issues cookie
– either using a named scheme, or default
var claims = new Claim[]{
new Claim("sub", "37734"),new Claim("name", "Brock Allen")
};
var ci = new ClaimsIdentity(claims, "password", "name", "role");var cp = new ClaimsPrincipal(ci);
await HttpContext.SignInAsync(cp);
13@leastprivilege
Cookies: Logging out• SignOutAsync removes cookie
await HttpContext.SignOutAsync();
14@leastprivilege
Data Protection
• Who thought this would be a good idea??
For giggles: "https://www.google.com/#q=<machineKey filetype:config"
<system.web><!– copied from http://machinekeys.ru seemed legit --><machineKey decryptionKey="656E7...617365206865726547A5"
validationKey="07C1493415E4405F08...6EF8B1F" /> </system.web>
15@leastprivilege
Data Protection
• Used to protect cookies and other secrets– IDataProtectionProvider in DI
• Uses a key container file– stored outside of application directory*– uses a key ring with automatic rotation– keys should be protected
• Needs to be synchronized between nodes in a farm
* https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/default-settings
16@leastprivilege
Claims Transformation
• Per-request manipulation of principal & claims– register an instance of IClaimsTransformation in DI– gets called from the handler's AuthenticateAsync method
public class ClaimsTransformer : IClaimsTransformation{
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal){
return await CreateApplicationPrincipalAsync(principal);}
}
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
17@leastprivilege
Authorization
• Complete re-write– better separation of business code and authorization logic– policy based authorization– resource/action based authorization– DI enabled
ASP.NET 4.x version: https://github.com/DavidParks8/Owin-Authorization
18@leastprivilege
[Authorize]
• Similar syntax– roles still supported
[Authorize]public class HomeController : Controller{
[AllowAnonymous]public IActionResult Index(){
return View();}
[Authorize(Roles = "Sales")]public IActionResult About(){
return View(User);}
}
19@leastprivilege
Authorization policies
services.AddAuthorization(options =>{
options.AddPolicy("ManageCustomers", policy =>{
policy.RequireAuthenticatedUser();policy.RequireClaim("department", "sales");policy.RequireClaim("status", "senior");
});});
[Authorize("ManageCustomers")]public IActionResult Manage(){
// stuff}
Startup
Controller
20@leastprivilege
Programmatically using policiespublic class CustomerController : Controller{
private readonly IAuthorizationService _authz;
public CustomerController(IAuthorizationService authz){
_authz = authz;}
public async Task<IActionResult> Manage(){
var result = await _authz.AuthorizeAsync(User, "ManageCustomers");if (result.Succeeded) return View();
return Forbid();}
}
21@leastprivilege
…or from a View
@using Microsoft.AspNetCore.Authorization@inject IAuthorizationService _authz
@if ((await _authz.AuthorizeAsync(User, "ManageCustomers")).Succeeded){
<div><a href="/customers/test">Manage</a>
</div>}
22@leastprivilege
Custom Requirementspublic class JobLevelRequirement : IAuthorizationRequirement
{
public JobLevel Level { get; }
public JobLevelRequirement(JobLevel level)
{
Level = level;
}
}
public static class StatusPolicyBuilderExtensions
{
public static AuthorizationPolicyBuilder RequireJobLevel(
this AuthorizationPolicyBuilder builder, JobLevel level)
{
builder.AddRequirements(new JobLevelRequirement(level));
return builder;
}
}
23@leastprivilege
Handling Requirementspublic class JobLevelRequirementHandler : AuthorizationHandler<JobLevelRequirement>{
private readonly IOrganizationService _service;
public JobLevelRequirementHandler(IOrganizationService service){
_service = service;}
protected override void Handle(AuthorizationContext context, JobLevelRequirement requirement)
{var currentLevel = _service.GetJobLevel(context.User);
if (currentLevel == requirement.Level){
context.Succeed(requirement);}
}}
24@leastprivilege
Resource-based Authorization
Subject ObjectOperation
- client ID- subject ID- scopes
- more claims
+ DI
- read- write- send via email- ...
- ID- owner
- more properties
+ DI
25@leastprivilege
Example: Document resource
public class DocumentAuthorizationHandler :AuthorizationHandler<OperationAuthorizationRequirement, Document>
{public override Task HandleRequirementAsync(AuthorizationHandlerContext context,OperationAuthorizationRequirement operation,Document resource)
{// authorization logic
}}
services.AddTransient<IAuthorizationHandler, DocumentAuthorizationHandler>();Add handler in DI:
26@leastprivilege
Invoking the authorization handlerpublic class DocumentController : Controller{
private readonly IAuthorizationService _authz;
public DocumentController(IAuthorizationService authz){
_authz = authz;}
public async Task<IActionResult> Update(Document doc){
if ((await _authz.AuthorizeAsync(User, doc, Operations.Update)).Failure){
return Forbid();}
// do stuff}
}
27@leastprivilege
Summary: Cookies & AuthorizationBrowser sends
request
Authentication middleware checks DefaultAuthenticate
scheme
Default authenticate handler calls
AuthenticateAsync
Cookie found?
PopulateHttpContext.User
Request arrives at Controller
[Authorize]?
Executeaction method
Get current user or call handler based
on specified scheme
AuthZ filter calls Challenge and
redirects to LoginPath
Is userauthenticated?
Account controller authenticates user and redirects back
Is userauthorized?
AuthZ filter calls Forbid and redirects to
AccessDeniedPath
yes
no
yes
no
no
yes
no
yes
28@leastprivilege
External Authentication
• ASP.NET Core supports– Google, Twitter, Facebook, Microsoft Account– OpenID Connect & JSON Web Tokens
• New generic OAuth 2.0 handler makes integration with other proprietary providers easier– LinkedIn, Slack, Spotify, WordPress, Yahoo, Github, Instragram, BattleNet,
Dropbox, Paypal, Vimeo…
https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
29@leastprivilege
Social Identity Providers
• Enabled with AddGoogle, et al.– Rely upon cookie authentication handler
services.AddAuthentication("Cookies").AddCookie("Cookies", options =>{
options.LoginPath = "/account/login";options.AccessDeniedPath = "/account/denied";
}).AddGoogle("Google", options =>{
options.SignInScheme = "Cookies";
options.ClientId = "...";options.ClientSecret = "...";
});
30@leastprivilege
Social Identity Providers
• Challenge triggers redirect for login– Control URL user returns to and state with AuthenticationProperties– MVC ChallengeResult works with action result architecture
var props = new AuthenticationProperties{
RedirectUri = "/Home/Secure"};await HttpContext.ChallengeAsync("Google", props);
// or if using MVC:
return Challenge("Google", props);
31@leastprivilege
Summary: External Authentication
Challenge(scheme)
Handler'sChallengeAsync
method getsinvoked
Redirect to externalprovider
External provider
Authentication middleware asks
handlers who wantsto process request
callback Handlers does theprotocol post-
processing
Call sign-in handler(sets cookie
containing externalidentity)
Redirect to final URL
32@leastprivilege
Mixing local and external Authentication
• Typically need provisioning logic for users from social providers– Use additional cookie handler for processing registration
services.AddAuthentication("Cookies").AddCookie("Cookies", options =>{
options.LoginPath = "/account/login";options.AccessDeniedPath = "/account/denied";
}).AddCookie("Temp").AddGoogle("Google", options =>{
options.SignInScheme = "Temp";
options.ClientId = "...";options.ClientSecret = "...";
});
33@leastprivilege
Mixing local and external Authentication
• Redirect page performs local account registration logic– AuthenticateAsync triggers cookie middleware– Create local account or load existing account– Use primary cookie middleware to log user in (and remove temp cookie)
var tempUser = (await HttpContext.AuthenticateAsync("Temp")).Principal;var userIdClaim = tempUser.FindFirst(ClaimTypes.NameIdentifier);var provider = userIdClaim.Issuer;var userId = userIdClaim.Value;
// create local account if new, or load existing local account
var user = new ClaimsPrincipal(...);await HttpContext.SignInAsync(user);await HttpContext.SignOutAsync("Temp");
34@leastprivilege
Summary: External Authentication with Callback
Challenge(scheme)
Handler'sChallengeAsync
method getsinvoked
Redirect to externalprovider
External provider
Authentication middleware asks
handlers who wantsto process request
callback Handlers does theprotocol post-
processing
Call sign-in handler(sets temporary
cookie containingexternal identity)
Run app-level post-processing
SignOut temp cookie
SignIn primary cookie
Redirect to final URL
35@leastprivilege
The way forward…
Browser
Native App
Server App"Thing"
Web App
Web API Web API
Web API
Security Token Service
Authentication, SSO, account linking, federation, social logins…
36@leastprivilege
Security Protocols
Browser
Native App
Server App"Thing"
Web App
Web API Web API
Web APIOpenID Connect
OAuth 2.0
OAuth 2.0
OAuth 2.0
OAuth 2.0
OAuth 2.0
OAuth 2.0
Security Token ServiceOpenID Connect
OpenID Connect
37@leastprivilege
http://openid.net/connect/
38@leastprivilege
OpenID Connect handler
services.AddAuthentication(options =>{
options.DefaultScheme = "Cookies";options.DefaultChallengeScheme = "oidc";
}.AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{
options.Authority = "https://demo.identityserver.io";options.ClientId = "mvc";
});
39@leastprivilege
Access Token Validation
• JWT bearer token authentication handler
public void ConfigureServices(IServiceCollection services){
services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>{
options.Authority = "https://your_oidc_provider";options.Audience = "your_api_identifier";
});}
40@leastprivilege
Issuing Tokens
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/community
41@leastprivilege
Certified OpenID ConnectProvider
42@leastprivilege
43@leastprivilege
Resources• https://github.com/leastprivilege/
– AspNetCoreSecuritySamples
• https://github.com/dotnet– corefx– coreclr
• https://github.com/aspnet– home– security– announcements
• https://docs.asp.net• https://identityserver.io
44@leastprivilege
thank you!