ict.ken.be

Delivering solid user friendly software solutions since the dawn of time.

Identity and Access Control in ASP.NET 4.5

Categories: Security

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

  1. user connects to his identity provider
  2. sends token to resource sts (which trusts identity provider)
  3. user gets transformed token back
  4. 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" />