OnValidateIdentity in OWIN Cookie authentication not called - asp.net

I am using the OWIN cookie authentication middleware and have setup a custom OnValidateIdentity-method that should be invoked on all requests that needs to be authenticated.
My setup looks like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "my-cookie",
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = async ctx =>
{
// my own validation code
}
}
}
The issue I have is that for some requests, OnValidateIdentity is not called. If I hit the same protected Web API controller multiple times, some of the requests would not invoke the OnValidateIdentity-method.
This leads to issues later in the processing when I need to use GetOwinContext().Authentication.User and the ClaimsPrincipal is not populated.
What could be the reason for this?

Found the issue. The cookie was expired.
This is because I also use the OpenIdConnect-middleware using the same cookie. Turns out that if you don't specify UseTokenLifetime = false in that config, it will use the expiry of the ID token as cookie expiry.

Related

Replacing Cookie by Token based authentication in ASP.NET OWIN OpenIdConnect code authorization flow

We have a web application written in ASP.NET that uses MVC for serving our Single Page Applications and Web API for ajax calls.
The authentication uses Microsoft.Owin and OpenIdConnect with Azure AD for Authority. The OAUTH flow is server side code authorization.
Then in Startup.Auth.cs we have
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
var cookieAuthenticationOptions = new CookieAuthenticationOptions()
{
CookieName = CookieName,
ExpireTimeSpan = TimeSpan.FromDays(30),
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SlidingExpiration = true,
};
app.UseCookieAuthentication(cookieAuthenticationOptions);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthorizationCodeReceived = (context) =>
{
/*exchange authorization code for a token
stored on database to access API registered on AzureAD (using ADAL.NET) */
},
RedirectToIdentityProvider = (RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context) =>
{
/* Set the redirects URI here*/
},
});
}
When clicking on signin we navigate to an url whose routes map to the methods of the following MVC controller
public class AccountController : Controller
{
public void SignIn(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext()
.Authentication.Challenge(authenticationProperties, OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public void SignOut(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
Then the end-user connected to our application is authenticated between our client apps and the ASP.net server using an ASP.NET cookie. We would like to use Token Based approach instead. If you are interested this is the reason.
I tried to replace
the Nuget package Microsoft.Owin.Security.Cookies by Microsoft.Owin.Security.OAuth and in Startup.cs
replacing
app.UseCookieAuthentication(cookieAuthenticationOptions); by app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
and in my AccountController we changed the challenge from HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType); to HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, OAuthDefaults.AuthenticationType);
The problem is that with Cookie the set-cookie was automatically sent in web request respond when the flow completes while redirecting to the url we specified.
Where can I find the Bearer generated by OWIN with UseOAuthBearerAuthentication (if there is any) **, **Where and When should I send it back to my client SPAs
Note: an open source sample of what we are trying to do can be found in this github repository.
I think there are two approaches for you to consider.
Use javascript libraries to perform sign-in & token acquisition within your single page app. Then your backend is purely an web API, and can just use OAuth bearer middleware to authenticate requests. The backend doesn't know anything about signing the user in. We have a good sample that takes this approach here. If your backend needs to make API calls as well, you could consider the OnBehalfOf flow as well. I usually recommend this approach.
Use the OpenIDConnect middleware in your server to perform user sign-in and token acquisition. You might even be able to omit the usage of the CookieAuthenticationMiddleware entirely (although I'm not 100% sure). You can capture the token in the AuthorizationCodeReceived notification as you mention, and you could redirect back to your SPA with the token in the fragment of the URL. You could also have some route which delivers the tokens (which are cached on your server) down to your javascript. In either case, you'll need to ensure that an outside caller can't get access to your tokens.
The thing to keep in mind will be how you refresh tokens when they expire. If you use #1, most of it will be handled for you by libraries. If you use #2, you'll have to manage it more yourself.

GetExternalLoginInfoAsync() loginInfo return null - but only after a few hours

I'm using Strava as my external login provider (I assume this is not related to Strava, could be google or facebook also) After running for a few hours / days or even weeks GetExternalLoginInfoAsync return null. I've read a bunch of other questions with the same problem, but did not find a solution. I post my entire ConfigureAuth method, just in case I did something wrong with the order.
If you have a strava account you could probably experience the problem here: fartslek.no/Account/Login
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
CookieManager = new SystemWebCookieManager()
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseStravaAuthentication( new StravaAuthenticationOptions{
ClientId="XXX",
ClientSecret= "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
});
}
I'm using this https://github.com/Johnny2Shoes/Owin.Security.Strava to get StravaAuth.
When it stop working a azure reset is not enough, but if I do a new deploy everything works for a while.
I'm using Owin 3.0.1 and Mvc 5.2.3
I had the same problem. After googling a little, I've discovered this is a known bug in Owin, because of the way they handle cookies.
This issue was submitted to Katana Team, but it looks they won't fix it at all. There are many workarounds for this, but this was the simplest I could find:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
ControllerContext.HttpContext.Session.RemoveAll();
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
See this question for more details about this bug, and let me know if this works well for you.

ASP.NET Identity 2 execute code after cookie authentication

I'm using ASP.NET Identity 2 authentication via OWIN middlewear. I've created a new project using the template so initially started with the default generated code but have changed it a bit (taken out entity framework and wired in my own existing authentication). This is all working.
What I'd now like to do is execute code after a user logs in via a saved cookie. I've had a look at ConfigureAuth in the Startup.Auth.cs file which I've configured as follows:
public void ConfigureAuth(IAppBuilder app) {
// Configure the user manager and signin manager to use a single instance
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider {
OnResponseSignIn = ctx => {
Log.Trace("On Response Sign In.");
},
OnResponseSignedIn = ctx => {
Log.Trace("On Response Signed In.");
},
OnValidateIdentity = async ctx => {
Log.Trace("On Validate Identity.");
}
}
});
}
From this I can see that OnResponseSignIn and OnResponseSignedIn are hit only during actual logins when the user enters their username and password. They are not hit when the user is authenticated via saved cookie.
OnValidateIdentity is hit regardless of whether the user authenticated via username/password or saved cookie and it's hit for every request they make.
What I'd like is to execute code just once after a login via cookie. Does anyone know how to do this? If not, I guess another option is to put code in OnValidateIdentity but in an if statement that will prevent it being run unless its the first call after the cookie authentication. Can anyone think of how to achieve that? All I can think of is to set a variable in Session after the code is first run and check for it's presence to prevent it being re-run?
It can probably be done by using a session variable as a flag, and only do your thing when it is not set.
OnValidateIdentity = async context => {
if (HttpContext.Current.Session["Refreshed"] == null)
{
/** do your thing **/
...
HttpContext.Current.Session["Refreshed"] = new object();
}
}

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