By Dominick Baier
- Authentication: Windows, Forms, Federated
- Authorization: Roles, Claims
ASP.Net security pipeline
- Request -> BeginRequest -> AuthenticateRequest
- Look for credential
- Set Principal (Thread.CurrentPrincipal = authUser; HttpContext.Current.User = authUser;) -> PostAuthenticateRequest
- Add claims to principal -> AuthorizeRequest
- Determine if user is allowed to access resource -> ExecuteHandler
- Resource Rendering -> EndRequest
- Post-processing (error codes, redirect) -> Response
Recommended way to get user is ClaimsPrincipal.Current (instead of Thread.CurrentPrincipal, HttpContext.Current.User, User)
Windows Authentication (Kerberos)
- Server 2012 allows for additional claims configuration
- Combination of IIS and ASP.Net settings
<system.webserver>
<security>
<authentication>
<anonymousAuthentication enabled="false" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webserver>
<authentication mode="Windows" />
Claims: Name, PrimarySid, GroupSid, DenyOnlySid
whoami /groups /fo list
whoami /claims (windows server 2012) ad://ext/...
Forms Authentication
- Produces only a Name Claim, but you can add additional claims using the Role Manager.
<system.webserver>
<security>
<authentication>
<anonymousAuthentication enabled="true" />
</authentication>
</security>
</system.webserver>
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
</system.web>
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var success = ValidateUser(model.UserName, model.Password);
if (success)
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToLocal(returnUrl);
}
}
ModelState.AddModelError("", "Username or password wrong.");
return View(model);
}
Membership & Role Manager
- Lots of methods on the interface you probably don't need.
- <roleManager cacheRolesInCookie="true" />
- Limited support for claims
Claims Transformation & Session Management
- to validate incoming identity data
- allows adding application specific claims to the principal
public class ClaimsTransformer : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (incomingPrincipal.Identity.IsAuthenticated)
{
return TransformClaims(incomingPrincipal);
}
return incomingPrincipal;
}
}
- ClaimsAuthenticationManager needs to be called after the authentication stage eg. PostAuthenticateRequest or using a HTTP module (WS-Federation plumbing)
Session security token
- var sessionToken = new SessionSecurityToken(principal, TimeSpan.FromHours(8));
<module runAllManagedModulesForAllRequests="true">
<add name="SessionAuthenticationModule" type="...System.IdentityModel.Services.SessionAuthenticationModule" :>
</modules>
- FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
- FederatedAuthentication.SessionAuthenticationModule.SignOut();
Events Pipeline
- SecurityTokenReceived
- SessionSecurityTokenCreated
- SignedIn/SignedOut
- SignOutError
Sliding Expiration
- use e.SessionToken to inspect session details in Received handler
if (extendSession)
{
var sam = sender as SessionAuthenticationModule;
e.SessionToken = sam.CreateSessionSecurityToken(...);
e.ReissueCookie = true;
}
Cookie Handling
- Chunked at 2KB
- DPAPI and machine key based protection
- web farms need shared key material
<securityTokenHandlers>
<remove type="... SessionSecurityTokenHandler ..." />
<add type="... MachineKeySessionSecurityTokenHandker ..." />
</securityTokenHandlers>
Server Side Caching
- only session token identifier gets serialized into a cookie
- needs server side (distributed) caching infrastructure
var sessionToken = new SessionSecurityToken(principal, TimeSpan.FromHours(8))
{
IsPersistent = false,
IsReferenceMode = true // cache on server
}
- Provide your own implementation of SessionSecurityTokenCache for appFabric or memCached.
ClaimsAuthenticationManager
- reject request based on missing identity information
Only authenticated users
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
Users in role Marketing only
<location path="customers">
<system.web>
<authorization>
<allow roles="Marketing" />
<deny users="*" />
</authorization>
</system.web>
</location>
ClaimsAuthorizationManager
<modules runAllManagedModulesForAllRequests="true">
<add name="ClaimsAuthorizationModule" type=" ... " />
</modules>
<system.identityModel>
<identityConfiguration>
<claimsAuthorizationManager type=" ... " />
</identityConfiguration>
</system.identityModel>
Intra-app Authorization (preferred)
[PrincipalPermission(SecurityAction.Demand, Roles="Marketing"]
public ActionResult AddCustomer() { ... }
[ClaimsPrincipalPermission(SecurityAction.Demand, Resource = "Customer", Operation = "Add")]
public ActionResult AddCustomer() { ... }
[Authorize(Roles = "Sales")] // mvc not in unit tests, no exception, limited to roles
public ActionResult AddCustomer() { ... }
[ClaimsAuthorize("Add", "Customer")] // Thinktecture.IdentityModel, also exists for webapi
public ActionResult AddCustomer() { ... }
var allowed = ClaimsAuthorization.CheckAccess("Get","Customer", id.ToString());
if (allowed) { ... }
[AllowAnonymous]
RegisterGlobalFilters
- filters.Add(new HandleErrorAttribute());
- filters.Add(new ClaimsAuthorizeAttribute());
-> don't mix authorization & business logic
External Authentication using WS-Federation
<authentication mode="None" />
<modules>
<add name="WSFederationAuthenticationModule" ... />
<add name="SessionAuthenticationModule" ... />
</modules>
- Testing with Idenity & Access visual studio extension (no jwt)
- WS-Federation allows to separate application from authentication which removes complexity from application
- Security token service responsible for authenticating user, authorization and emitting token + claims
<wsFederation passiveRedirectEnable="true" issuer="https://idsrv/issue/wsfed" realm="http://myapp" />
<issuerNameRegistry type="... ConfigurationBasedIssuerNameRegistry ...">
<trustedIssuers>
<add thumbprint="..." name="STS" />
</trustedIssuers>
</issuerNameRegistry>
<certificateValidation certificateValidationMode="ChainTrust" revocationMode="Online" />
<audienceUris>
<add value="http://myapp" />
</audienceUris>
- Most configuration can be derived from federation metadata (https://idsrv/FederationMetadata/2007-06/FederationMetadata.xml)
Dynamic Configuration
- Application_Start: FederationAuthentication.FederationConfigurationCreated += FederationAuthentication_FederationConfigurationCreated;
WS-Federation Events
- SecurityTokenReceived
- SecurityTokenValidated
- SessionSecurityTokenCreated
- SignedIn/SignedOut
- SignInError/SignOutError
- RedirectingToIdentityProvider
var signIn = new SignInRequestMessage(
new Uri("https://idsrv/issue/wsfed"), "http://fooRealm");
var url = signIn.WriteQueryString();
- FederatedAuthentication.WSFederationAuthenticationModule.SignOut(); -> does not sign-out the user at the token service
- Server-side Session Caching & Sliding Expiration
- SessionSecurityTokenCreated: e.SessionToken.IsReferenceMode = true;
Advanced Federation Patterns
Single Sign-On
- identity provider establishes a logon session with user
- identity provider is shared across multiple applications
Single Sign-Out
- /wsfed?wa=wsignout1.0
- page back to browser that clears out all rps
- <img src="https://rp1/?wa=wsignoutcleanup1.0" />
public ActionResult SignOut()
{
var fam = FederatedAuthentication.WSFederationAuthenticationModule;
//clear local cookie
fam.SignOut(isIPRequest: false);
//initiate a federated sign out request to the sts.
var signOutRequest = new SignOutRequestMessage(
new Uri(fam.Issuer), fam.Realm
);
return new RedirectResult(signOutRequest.WriteQueryString());
}
Federating with multiple Identity Providers
- Relying party only "knows" about the R-STS (Resource-STS)
Logical Model
- user connects to his identity provider
- sends token to resource sts (which trusts identity provider)
- user gets transformed token back
- user uses token with relying party (which trusts the R-sts)
Physical Model (Home Realm Discovery HRD)
- user logs into relying party, so browser has to show a selection UI and afterwards remember the decision
- the whr parameter in WS-Federation allows to pre-select the identity provider
- https://idsrv/issue/hrd/?wa=wsignin1.0&wtrealm=https://www.myapp.com&whr=[identifier_of_external_idp]
- <wsFederation homeReal="web" />