ASP .NET Core cookie authentication - asp.net

I have an ASP.NET Web API site that is using an authentication cookie generated from another ASP.NET Web Form site.
Since both the ASP.NET Web API site and the ASP.NET Web Form site have the same "machineKey" and same "form auth cookie name", it would allow a user to login to the "Web Form Site" and then pull data from the "Web API Site" without re-authentication since the "auth" cookie would pass between sites.
All this was done inside that web.config and did not require any special code to work.
Now we would like to create an ASP NET Core Web API site and have that same auth cookie work.
I can't seem to find any good articles on how do this in .NET Core.
There are plenty of articles on how to us ASP Identity which we don't have and plenty of articles on auth cookies but none explaining how to get it from another site...
Here is the web.config sections that I had in the Web API:
<system.web>
<authentication>
<forms name=".TESTAUTH" cookieless="AutoDetect" requireSSL="true" domain=".test.com" slidingExpiration="true" protection="All" timeout="600"
enableCrossAppRedirects="true" />
</authentication>
<!-- This is used to share the auth cookie between web sites -->
<machineKey validationKey="SOMEKEY"
decryptionKey="SOMEOTHERKEY" validation="SHA1" decryption="AES" compatibilityMode="Framework20SP2" />
</system.web>
EDIT A
We are using "Framework20SP2" and that seems to complicate the issue.
Here is a link to some code that will decrypt the auth ticket based on "Framework20SP2"
https://github.com/dazinator/AspNetCore.LegacyAuthCookieCompat
If I use the NuGet above, it does decrypt the auth cookie in the .NET Core site but I can't seem to figure out how to get that to work in the .NET Core DataProtectionProvider.

The first thing you need to confirm is that the applications are hosted on same domain or subdomains of that domain . Otherwise consider using token/sso service like Identity Server 4/Azure AD to implement SSO .
ASP.NET 4.x apps that use Katana Cookie Authentication Middleware can be configured to generate authentication cookies that are compatible with the ASP.NET Core Cookie Authentication Middleware .Install the Microsoft.Owin.Security.Interop package into ASP.NET 4.x app to use DataProtectionProvider , which provides data protection services for the encryption and decryption of authentication cookie payload data.
You can now remove machineKey config , the Katana cookies middleware uses data protection , that doesn't rely on machine keys but on a key ring persisted ,you should ensure that it uses a persisted key storage, and that key storage is accessible and used by each application .
See below article for more details and code sample :
https://learn.microsoft.com/en-us/aspnet/core/security/cookie-sharing?view=aspnetcore-3.0

While this works, I'm not 100% sure it is the best / proper way of doing this but
here is the main class I created.
public class FormsAuthCookieTicketFormat : ISecureDataFormat<AuthenticationTicket>
{
private LegacyFormsAuthenticationTicketEncryptor _Encryptor;
private Sha1HashProvider _HashProvider;
public FormsAuthCookieTicketFormat(string decryptionKey, string validationKey)
{
byte[] decryptionKeyBytes = HexUtils.HexToBinary(decryptionKey);
byte[] validationKeyBytes = HexUtils.HexToBinary(validationKey);
_Encryptor = new LegacyFormsAuthenticationTicketEncryptor(decryptionKeyBytes, validationKeyBytes, ShaVersion.Sha1, CompatibilityMode.Framework20SP2);
_HashProvider = new Sha1HashProvider(validationKeyBytes);
}
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var ticket = _Encryptor.DecryptCookie(protectedText);
var identity = new ClaimsIdentity("MyCookie");
identity.AddClaim(new Claim(ClaimTypes.Name, ticket.Name));
identity.AddClaim(new Claim(ClaimTypes.IsPersistent, ticket.IsPersistent.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Expired, ticket.Expired.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Expiration, ticket.Expiration.ToString()));
identity.AddClaim(new Claim(ClaimTypes.CookiePath, ticket.CookiePath));
identity.AddClaim(new Claim(ClaimTypes.Version, ticket.Version.ToString()));
// Add some additional properties to the authentication ticket.
var props = new AuthenticationProperties();
props.ExpiresUtc = ticket.Expiration.ToUniversalTime();
props.IsPersistent = ticket.IsPersistent;
var principal = new ClaimsPrincipal(identity);
var authTicket = new AuthenticationTicket(principal, props, CookieAuthenticationDefaults.AuthenticationScheme);
return authTicket;
}
}
Here is the Startup.cs file
var formsCookieFormat = new FormsAuthCookieTicketFormat(decryptionKey, validationKey);
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = ".TESTCOOKIE";
options.Cookie.Domain = ".TESTDOMAIN";
options.Cookie.Path = "/";
options.Cookie.HttpOnly = false;
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.AccessDeniedPath = "/Login/";
options.LoginPath = "/Login/";
options.ReturnUrlParameter = "returnurl";
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.TicketDataFormat = formsCookieFormat;
options.SlidingExpiration = true;
});

Related

Owin / Mvc add support for bearer token authentication

My objective is to have an Asp.Net Mvc action secured with OpenId authentication, and support 2 types of clients: browser and a native WPF application. The STS I will use is ADFS 2016.
Currently clients browsers works well. For this, I have UseOpenIdConnectAuthentication configured in my startup class.
I'm able to call my Mvc action (secured with Authorize attribute), user is redirected to STS, and once authentication is done, I come back to my Mvc action with a ClaimsIdentity properly filled.
Now I'm trying to have a native WPF app able to authenticate to the same Mvc action in the same Web app, and things are getting tricky.
On the client side (my WPF application), I'm using ADAL and the following code:
var authContext = new AuthenticationContext("<MySTSUri>");
var authResult = await authContext.AcquireTokenAsync(
"http://localhost:1276/openid/login",
"MyNativeAppId",
new Uri("myapp://openid"),
new PlatformParameters(PromptBehavior.Auto),
UserIdentifier.AnyUser,
"");
if (!string.IsNullOrEmpty(authResult.AccessToken))
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(authResult.AccessTokenType, authResult.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync("http://localhost:1276/openid/login");
if (response.IsSuccessStatusCode)
{
var text = await response.Content.ReadAsStringAsync();
}
}
}
The problem is basically that I can't tell the Web app to be able to validate this type of ADAL request.
I've tried various things in the Web application Owin startup file configuration:
leaves UseOpenIdConnectAuthentication: it doesn't seem sufficient, I'm redirected to STS with the ClientId of the Web application
UseActiveDirectoryFederationServicesBearerAuthentication api since I know my STS will always be an ADFS
UseOAuthBearerAuthentication
None of them are working.
Please can someone help how to achieve this?
Am I going in the right direction?
Any ideas/pointers would be greatly appreciated.
Thanks,
Alex
I've managed to get it working. I post the answer for the record.
What helped me a lot is to enable Owin logs in the web.config:
<system.diagnostics>
<switches>
<add name="Microsoft.Owin" value="Verbose" />
</switches>
</system.diagnostics>
Then with Owin, you can simply chain multiple authentication methods. So in my case, I've just used:
app.UseActiveDirectoryFederationServicesBearerAuthentication(
new ActiveDirectoryFederationServicesBearerAuthenticationOptions
{
MetadataEndpoint = adfsMetadataEndpoint,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudiences = new[] { validAudience }
}
});
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
AuthenticationType = "OpenId",
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
Scope = "openid",
SignInAsAuthenticationType = "Cookies"
});
Cheers,
Alex

Integrating ASP.NET code to Active Directory or LDAP after deploying on Bluemix

I'm working on an ASP.Net project, which needs to be deployed after completion on PaaS, which needs to be BlueMix (It wasn't my choice, It was an order).
In addition I need to use an:
Active Directory or LDAP to the User Authentication and Authorization, integrated with the ASP.Net Project.
The Issues Here Are :
1. I have found an integration to the Active Directory or SSO Services using only Java or Node.js, but in my case I am using ASP.Net
2. I want a solution for how the integration can be done on top of the PaaS between the Active Directory and ASP.Net application.
Depending on which version of ADFS you're using, you should be able to use either OAuth or OIDC middleware to connect from an ASP.NET Core application (assuming you're using ASP.NET Core because you're using Bluemix). If you're using at least ADFS 3.0 (Windows Server 2012+), you can use ASP.NET Core's generic OAuth middleware to connect.
First, create a configuration file to store your ADFS server settings, or modify an existing configuration file (such as appsettings.json).
Sample "adfs-settings.json" file:
{
"ADFS": {
"ClientId": "Your ClientId as set on ADFS server",
"ResourceUrl": "url of this application (ex: http://mywebapp.mybluemix.net)",
"ServerUrl": "url of ADFS (ex: https://dc.your.domain)"
}
}
If you created a new file, such as "adfs-settings.json", for your ADFS configuration, add it to your Configuration object in the constructor of your Startup.cs file.
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("adfs-settings.json");
Configuration = builder.Build();
}
In your Configure method of Startup.cs create an OAuthOptions object:
var options = new OAuthOptions();
options.AutomaticChallenge = true;
options.AuthenticationScheme = "ADFS";
Specify the ClientId that you created when configuring this application on your ADFS server by reading it from your Configuration object. The generic OAuth middleware also requires that you provide a ClientSecret here even though that value is not actually used by ADFS 3.0.
options.ClientId = Configuration["ADFS:ClientId"];
options.ClientSecret = "ADFS 3.0 does not support confidential client, but OAuth middleware requires it";
Set the callback url which the ADFS server will redirect to in your application.
options.CallbackPath = new PathString("/signin-adfs");
Now configure the OAuthEvents. OnRedirectToAuthorizationEndpoint defines parameters which are passed to the ADFS authorization endpoint when the application determines that a user needs to be authorized. This will require at least a resource parameter which points to the url of your application. OnCreatingTicket is triggered when the ADFS server has finished authorizing a client and returns a JWT token containing claims data to your application. In this method you'll need to process adding roles and claims to the HttpContext object.
options.Events = new OAuthEvents {
OnRedirectToAuthorizationEndpoint = context =>
{
var parameter = new Dictionary<string, string>
{
["resource"] = Configuration["ADFS:ResourceUrl"]
};
var query = QueryHelpers.AddQueryString(context.RedirectUri, parameter);
context.Response.Redirect(query);
return Task.CompletedTask;
},
OnCreatingTicket = context => {
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
JwtSecurityToken validatedToken = tokenHandler.ReadJwtToken(context.AccessToken);
IEnumerable<Claim> a = validatedToken.Claims;
foreach (var claim in a)
{
// role claim needs to be mapped to http://schemas.microsoft.com/ws/2008/06/identity/claims/role
// for IsInRole() to work properly
if (claim.Type == "role")
{
context.Identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value));
}
else if (claim.Type == "unique_name")
{
// map name to Identity.Name
context.Identity.AddClaim(new Claim(context.Identity.NameClaimType, claim.Value));
}
else
{
// this is optional, if you want any other specific claims from Active Directory
// this will also include some information about the jwt token such as the issue
// and expire times
context.Identity.AddClaim(new Claim(claim.Type, claim.Value));
}
}
return Task.CompletedTask;
}
};
Next, set the ClaimsIssuer to the ADFS url and set the SignInScheme to CookieAuthenticationDefaults.AuthenticationScheme and configure the AuthorizationEndpoint and TokenEndpoint to the proper endpoints on your ADFS server.
options.ClaimsIssuer = Configuration["ADFS:ServerUrl"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.AuthorizationEndpoint = Configuration["ADFS:ServerUrl"] + "/adfs/oauth2/authorize/";
options.TokenEndpoint = Configuration["ADFS:ServerUrl"] + "/adfs/oauth2/token/";
Finally, add the OAuth middleware using the options you've just created:
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOAuthAuthentication(options);
Now you should be able to apply the [Authorize] attribute to any controller or action which requires authorization with ADFS. For a complete sample application see this GitHub repo.

Single Sign On with Azure Active Directory

I have a bunch of web apps that I've created. Some of them use old-style webforms authentication, and some other sites at our company use different authentication patterns.
I'm trying to consolidate this, under one SSO pattern using Azure Active Directory. I've been trying to following guides/tutorials but it's not clicking for me.
My tech is currently ASP 4/MVC 5, although if ASP 5/MVC 6 is easier then I have the freedom to go that route as well. All web apps are hosted in Azure currently.
The confusion for me comes in that while looking through documentation, there seem to be so many ways to authentication and authorize users (also, authenticate vs authorize isn't absolutely clear to me).
I went to the Active Directory area of Azure Management portal (the old one). I added a new application called TestApp. I set the app URL to https://localhost:44320/, then sign-on URL to https://localhost:44320/, the tenant name to testapp. And my reply URL is https://localhost:44320/
This makes my app id uri https://localhost:44320/testapp I think? I also have my client ID guid.
The tutorial has an AccountController with a SignIn method like this:
public void SignIn()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
When navigating to this, I receive the following in the browser:
[InvalidOperationException: IDX10803: Unable to create to obtain configuration from: 'https://localhost:44320/testapp/.well-known/openid-configuration'.]
I have a feeling it's because Azure is unable to redirect this all to back to my localhost? How can I set this up so I can test it out on Azure itself? And even further than that, will this solution be usable from multiple webapps? I'd assume they'd each be different applications in my Active Directory, but they'd all need to use a SSO procedure, where users can sign into multiple apps with one identity.
Sorry for any confusion, this is all very convoluted to me but I am trying to learn it as best I can.
Edit:
In the Startup of the webapp, I am calling this:
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
Which utilizes these app.config settings:
<add key="ida:ClientId" value="[my client id here]" />
<add key="ida:Tenant" value="testapp" />
<add key="ida:AADInstance" value="https://localhost:44320/{0}" />
<add key="ida:PostLogoutRedirectUri" value="https://localhost:44320/" />
Azure is able to redirect to localhost, it will just pop up a security confirmation asking if its ok to navigate to localhost.
Your tenant in app.config doesn't look right, change these app settings:
<add key="ida:Tenant" value="[YOUR TENANT].onmicrosoft.com" />
<add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
To find out more about your tenant see this article: How to get an Azure Active Directory tenant
You can also try adding this code to your Notifications in Startup (just under AuthenticationFailed), try putting breakpoints on the handlers to see what happens:
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
return Task.FromResult(0);
}
Put an [Authorize] attribute on one of your controllers and it should redirect to the AAD authentication when you browse to it.
AFAIK each app would need a separate application in Azure AD and you would need to implement this Authentication in each separate app. I have managed to have a seamless sign in experience when I link via url from one app to another.
This answer sums up authentication vs authorization nicely: Authentication versus Authorization

How should I be handling authentication with Identity 2.0 and WebAPI 2.1 and Owin 2?

I'm using
New browser only clients on the same domain
Identity 2.0
WebAPI 2.1
Owin 2.1
AngularJS front-end for registration, login and data display
In a WebAPI application with an AngularJS front-end.
I'm reading about token authentication but I am very confused now and I cannot find any good examples out there that use my combination. What I would like to know is should I be using cookies or tokens for the authentication. Should I be using a Userfactory or the CreatePerOwinContext?
Here's what I have in my Startup.Auth.cs
public partial class Startup {
public void ConfigureAuth(IAppBuilder app) {
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/"),
Provider = new CookieAuthenticationProvider {
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
}
Here's my WebAPI config:
public static class WebApiConfig
{
public static void CustomizeConfig(HttpConfiguration config)
{
config.Formatters.Remove(config.Formatters.XmlFormatter);
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
json.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-ddTHH:mmZ" });
}
I saw some examples using this code but I am not sure how I can call this:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
Could I just replace the cookie authentication with this?
Not an expert, but in my dabbling I've found that tokens work great for api and from javascript to api, and traditional cookies lean mostly for a ui. Either or both will work depending on what your trying to do.
You can follow something like this link that does cookie for the ui and token for the api http://blog.iteedee.com/2014/03/asp-net-identity-2-0-cookie-token-authentication/
app.CreatePerOwinContext(ApplicationSession.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Token Authentication
app.UseOAuthBearerAuthentication(new OAuthBearerOptions());
I think you can set the cookie authentication options authentication type to bearer if you want bearer for both, but you would have to play with it. The token would be in the owincontext under ".AspNet.ExternalBearer".
I also think if you register the Identity 2.0 middleware i think it also registers the oauth middleware stuff so you don't need to register the oauthserver middleware yourself. Thats the OAuthAuthorizationServerOptions code you posted. You dont need it.
if the ui and api are in separate then its a bit harder if you want to do some sort of single sign on from the ui pass to the api. I would recommend looking at opensource identity server or authorization server from thinktecture.
If your set on owin middleware and Identity 2.0 you would need to make sure the token can be read by both application and api and you probably would need to implement ISecureDataFormat. But remember, decryption doesn't mean you can 100% trust a token, it should be signed and verified. Depends on your needs.
Sorry, I guess thats a long ramble... Good luck.

Server side claims caching with Owin Authentication

I have an application that used to use FormsAuthentication, and a while ago I switched it to use the IdentityModel from WindowsIdentityFramework so that I could benefit from claims based authentication, but it was rather ugly to use and implement. So now I'm looking at OwinAuthentication.
I'm looking at OwinAuthentication and the Asp.Net Identity framework. But the Asp.Net Identity framework's only implementation at the moment uses EntityModel and I'm using nHibernate. So for now I'm looking to try bypassing Asp.Net Identity and just use the Owin Authentication directly. I was finally able to get a working login using the tips from "How do I ignore the Identity Framework magic and just use the OWIN auth middleware to get the claims I seek?", but now my cookie holding the claims is rather large. When I used the IdentityModel I was able to use a server side caching mechanism that cached the claims on the server and the cookie just held a simple token for the cached information. Is there a similar feature in OwinAuthentication, or would I have to implement it myself?
I expect I'm going to be in one of these boats...
The cookie stays as 3KB, oh well it's a little large.
Enable a feature similar to IdentityModel's SessionCaching in Owin that I don't know about.
Write my own implementation to cache the information causing the cookie to bloat and see if I can hook it up when I configure Owin at application startup.
I'm doing this all wrong and there's an approach I've not thought of or I'm misusing something in Owin.
public class OwinConfiguration
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Application",
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true,
CookieName = "Application",
ExpireTimeSpan = TimeSpan.FromMinutes(30),
LoginPath = "/Login",
LogoutPath = "/Logout",
ReturnUrlParameter="ReturnUrl",
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
//handle custom caching here??
}
}
//CookieName = CookieAuthenticationDefaults.CookiePrefix + ExternalAuthentication.ExternalCookieName,
//ExpireTimeSpan = TimeSpan.FromMinutes(5),
});
}
}
UPDATE
I was able to get the desired effect using the information Hongye provided and I came up with the below logic...
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
var userId = context.Identity.GetUserId(); //Just a simple extension method to get the ID using identity.FindFirst(x => x.Type == ClaimTypes.NameIdentifier) and account for possible NULLs
if (userId == null) return;
var cacheKey = "MyApplication_Claim_Roles_" + userId.ToString();
var cachedClaims = System.Web.HttpContext.Current.Cache[cacheKey] as IEnumerable<Claim>;
if (cachedClaims == null)
{
var securityService = DependencyResolver.Current.GetService<ISecurityService>(); //My own service to get the user's roles from the database
cachedClaims = securityService.GetRoles(context.Identity.Name).Select(role => new Claim(ClaimTypes.Role, role.RoleName));
System.Web.HttpContext.Current.Cache[cacheKey] = cachedClaims;
}
context.Identity.AddClaims(cachedClaims);
}
}
OWIN cookie authentication middleware doesn't support session caching like feature yet. #2 is not an options.
#3 is the right way to go. As Prabu suggested, you should do following in your code:
OnResponseSignIn:
Save context.Identity in cache with a unique key(GUID)
Create a new ClaimsIdentity embedded with the unique key
Replace context.Identity with the new identity
OnValidateIdentity:
Get the unique key claim from context.Identity
Get the cached identity by the unique key
Call context.ReplaceIdentity with the cached identity
I was going to suggest you to gzip the cookie, but I found that OWIN already did that in its TicketSerializer. Not an option for you.
Provider = new CookieAuthenticationProvider()
{
OnResponseSignIn = async context =>
{
// This is the last chance before the ClaimsIdentity get serialized into a cookie.
// You can modify the ClaimsIdentity here and create the mapping here.
// This event is invoked one time on sign in.
},
OnValidateIdentity = async context =>
{
// This method gets invoked for every request after the cookie is converted
// into a ClaimsIdentity. Here you can look up your claims from the mapping table.
}
}
You can implement IAuthenticationSessionStore to store cookies into database.
Here's example for storing cookie in redis.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SessionStore = new RedisSessionStore(new TicketDataFormat(dataProtector)),
LoginPath = new PathString("/Auth/LogOn"),
LogoutPath = new PathString("/Auth/LogOut"),
});
Check out full example at here

Resources