Overview
The new security feature design for MVC 5 is based on OWIN authentication middleware. The benefit for it is that security feature can be shared by other components that can be hosted on OWIN. Since the Katana team did a great effort to support the OWIN integrated pipeline in ASP.NET, it can also secure apps hosted on IIS, including ASP.NET MVC, Web API, Web Form.
Forms authentication uses an application ticket that represents user’s identity and keeps it inside user agent’s cookie. When user first accesses a resource requiring authorization, it will redirect user to login page. After the user provides credentials, your application code will validate the user name and password and build user claims including user’s name, roles, etc. After passing claims to the Forms authentication middleware, it will convert it to an application ticket and serialize, encrypt and encode it into a ticket token. Then, send it out as a cookie. When the next time user sends request with the cookie, the middleware will validate it and convert the ticket token back to claims principal and save it in HttpContext.User, which will shared across ASP.NET pipeline.
ASP.NET also has a forms authentication support through the FormsAuthenticationModule, which, however, can only support applications hosted on ASP.NET and doesn’t have claim support . Here is a rough feature comparison list:
Features |
Asp.Net Forms Authentication |
OWIN Forms Authentication |
Cookie Authentication |
Yes |
Yes |
Cookieless Authentication |
Yes |
No |
Expiration |
Yes |
Yes |
Sliding Expiration |
Yes |
Yes |
Token Protection |
Yes |
Yes |
Claims Support |
No |
Yes |
Web Farm Support |
Yes |
Yes |
Unauthorized Redirection |
Yes |
Yes |
In this blog, you will learn:
· Creating an MVC project with OWIN Forms authentication enabled.
· Understanding OWIN Forms authentication options.
· Understanding Application Sign In Cookie flow.
· Understanding External Sign In Cookie flow.
· Working with new Identity API
Creating MVC project with OWIN Forms authentication enabled
To get started, you need to create new MVC .
· Make sure you have installed:
· In Visual Studio 2013, select New Project from File menu
· In New Project dialog, select Installed Template / Visual C# / Web / ASP.NET Web Application
· In New ASP.NET Project dialog, select MVC project template
Optional: On the right panel of the dialog, you can select Configure Authentication, to choose No Authentication, Individual User Accounts, Organization Authentication and Windows Authentication. In this tutorial, we use Individual User Accounts, which is the default setting.
· Click Create Project button
In the new project, open the App_Start/Startup.Auth.cs file. It has the following code:
- publicpartialclassStartup
- {
- // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
- publicvoid ConfigureAuth(IAppBuilder app)
- {
- // 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
- app.UseSignInCookies();
- // Uncomment the following lines to enable logging in with third party login providers
- //app.UseMicrosoftAccountAuthentication(
- // clientId: “”,
- // clientSecret: “”);
- //app.UseTwitterAuthentication(
- // consumerKey: “”,
- // consumerSecret: “”);
- //app.UseFacebookAuthentication(
- // appId: “”,
- // appSecret: “”);
- //app.UseGoogleAuthentication();
- }
- }
Note that UseSignInCookies must be called before any external login providers.
Understanding OWIN Forms authentication options
The UseSignInCookies extension method actually registers two cookie authentications. (You can see the source for the methods below at at: http://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.Forms/FormsAuthenticationExtensions.cs )
- publicstaticvoid UseSignInCookies(thisIAppBuilder app)
- {
- app.UseApplicationSignInCookie();
- app.UseExternalSignInCookie();
- }
Both the UseApplicationSignInCookie and the UseExternalSignInCookie extension methods call UseFormsAuthentication, but with different settings.
- publicstaticIAppBuilder UseApplicationSignInCookie(thisIAppBuilder app)
- {
- return UseFormsAuthentication(app, new FormsAuthenticationOptions
- {
- AuthenticationType = FormsAuthenticationDefaults.ApplicationAuthenticationType,
- AuthenticationMode = AuthenticationMode.Active,
- CookieName = FormsAuthenticationDefaults.CookiePrefix + FormsAuthenticationDefaults.ApplicationAuthenticationType,
- LoginPath = FormsAuthenticationDefaults.LoginPath,
- LogoutPath = FormsAuthenticationDefaults.LogoutPath,
- });
- }
- publicstaticIAppBuilder UseExternalSignInCookie(thisIAppBuilder app)
- {
- app.SetDefaultSignInAsAuthenticationType(FormsAuthenticationDefaults.ExternalAuthenticationType);
- return UseFormsAuthentication(app, new FormsAuthenticationOptions
- {
- AuthenticationType = FormsAuthenticationDefaults.ExternalAuthenticationType,
- AuthenticationMode = AuthenticationMode.Passive,
- CookieName = FormsAuthenticationDefaults.CookiePrefix + FormsAuthenticationDefaults.ExternalAuthenticationType,
- ExpireTimeSpan = TimeSpan.FromMinutes(5),
- });
- }
The Application sign in cookie is used to authenticate users for the current application, while external sign in cookie is used to authenticate users from external providers, like Facebook, Google, Twitter and Microsoft account. If you want to change the default authentication options, you can use UseFormsAuthentication extension method to change them.
Here are list of options that you can change in UseFormsAuthentication:
- app.UseFormsAuthentication(new FormsAuthenticationOptions()
- {
- AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
- AuthenticationType = “MyApplication”,
- CookieDomain = “.myapp.com”,
- CookieHttpOnly = true,
- CookieName = “.AspNet.MyApplication”,
- CookiePath = “/Account”,
- CookieSecure = CookieSecureOption.Always,
- ExpireTimeSpan = TimeSpan.FromDays(1),
- LoginPath = “/Account/Login”,
- ReturnUrlParameter = “return_url”,
- SlidingExpiration = true,
- Provider = new FormsAuthenticationProvider()
- {
- OnResponseSignin = async ctx =>
- {
- Console.WriteLine(“OnResponseSignin”);
- PrintClaimsIdentity(ctx.Identity);
- },
- OnValidateIdentity = async ctx =>
- {
- Console.WriteLine(“OnValidateIdentity”);
- PrintClaimsIdentity(ctx.Identity);
- }
- }
- });
Options |
Description |
ApplicaitonSignInCookie Default Values |
ExternalSignInCookie Default Values |
AuthenticationMode |
If Active the authentication middleware alters the requested user coming in and returns 401 Unauthorized responses going out. If Passive the authentication middleware will only provide identity and alter responses when explicitly indicated by the AuthenticationType. |
Active |
Passive |
AuthenticationType |
The AuthenticationType in the options corresponds to the IIdentity.AuthenticationType property. A different value may be assigned in order to use the same authentication middleware type more than once in a pipeline. |
“Application” |
“External” |
CookieDomain |
Defines the domain that cookie is under |
<null> |
<null> |
CookieHttpOnly |
Defines if the cookie is http only. It’s true by default. |
True |
True |
CookieName |
Defines the name of the cookie |
“.AspNet.Application” |
“.AspNet.External” |
CookiePath |
Defines the path that cookie is under. By default, it’s /. |
“/” |
“/” |
CookieSecure |
Defines if the cookie will only be sent back to HTTPS URL. By default, it is SameAsRequest, which means If the URI that provides the cookie is HTTPS, then the cookie will only be returned to the server on subsequent HTTPS requests. Otherwise if the URI that provides the cookie is HTTP, then the cookie will be returned to the server on all HTTP and HTTPS requests. |
SameAsRequest |
SameAsRequest |
ExpireTimeSpan |
Defines the expiration of the cookie. |
14 days |
5 minutes |
LoginPath |
Defines the Login path when unauthorized request will be redirected to. |
“/Account/Login” |
<null> |
ReturnUrlParameter |
Defines the return URL parameter name, which tells your application the URL of previous unauthorized request to redirect to after login. Your application code is responsible for retrieving it and redirecting the user agent to the return URL |
“ReturnUrl” |
<null> |
SlidingExpiration |
Defines if the authentication supports sliding expiration, which will automatically extends the expiration time if user session is still active. By default, it’s true. |
True |
False |
Provider |
The forms authentication provider that can intercept events during sign in and validate identity. · OnResponseSignin: happens just before set-cookie is sent out · OnValidateIdentity: happens just after incoming cookie is parsed into ClaimsIdentity |
<null> |
<null> |
Understanding Application Sign in Cookie flow
Active mode is similar to what the old ASP.NET forms authentication module did, while passive is a way to let framework code control the authentication explicitly.
ApplicatinSignInCookie is an active forms authentication middleware, so when a valid cookie is returned, it will:
· Automatically redirect an unauthorized response to the login page.
· Set the logged in user principal to HttpContext.User, so the rest of ASP.NET pipeline will know what user is authenticated.
The following is a basic flow of application forms authentication.
User Agent |
Forms Authentication Middleware(Application) |
Web App |
1. Get /Account/Manage —————————-> |
||
2. Response Status: 401 AccountController is protected by Authroize attribute, so unauthorized request will return a 401 error. [Authorize] public class AccountController : Controller { } <—————————- |
||
3. Alter response status to 302 and redirect to /Application/Login?ReturnUrl=/Account/Manage The application sign in cookie is in active authentication mode and it will automatically redirect to login page when there is a 401 response. <—————————- |
||
4. GET /Application/Login?ReturnUrl=/Account/Manage —————————-> |
||
5. Response Status: 200 and with login page in body <—————————- |
||
6. POST /Application/Login?ReturnUrl=/Account/Manage User input user name and password and post back to server —————————-> |
||
7. Status: 301 Location: /Account/Manage Server code does: a. Validating user credentials b. Calling IdentityAuthenticationManager.SignIn to sign in with application sign in cookie c. Redirecting to returnUrl <—————————- |
||
8. Status: 302 Location: /Account/Manage Set-Cookie: .AspNet.Application=<Ticket Token> The middleware will convert user claims with extra data into ticket token and set it in cookie. <—————————- |
||
9. GET /Account/Manage Cookie: .AspNet.Application=<Ticket Token> —————————-> |
||
10. Validate <Ticket Token> and convert it to claims identity and set it to HttpContext.User —————————-> |
||
11. Status: 200 with manage account page in body Authorize attribute sees that the identity is authenticated from HttpContext.User. So allow the request to reach the action. <—————————- |
Understanding External Sign in Cookie flow
ExternalSignInCookie is a passive forms authentication, which is unobtrusive to your application if you don’t explicitly ask it to do something. Your application code can explicitly ask it to provide the user identity or alter the response to set cookie or remove cookie. To demo external sign in cookie, you need to configure an external provider like Facebook. This flow chart starts from the point Facebook authentication middleware receives the user info from Facebook graph API. For the detailed flow for external provider sign in process, please check out Robert’s tutorial: External Authentication Services
User Agent |
Forms Authentication Middleware(Application) |
Forms Authentication Middleware(External) |
Facebook Authentication Middleware |
Web App |
<After facebook returns authorization code back to your app> 1. GET /signin-facebook?code=<authorization code>&state=<state> —————————-> |
||||
2. Status: 302 Location: /Account/ExternalLoginCallback?loginProvider=Facebook Middleware code does: a. Get access token by authorization code from facebook b. Get user graph data from facebook c. Convert user graph data into claims identity d. Sign in claims identity as external type <—————————- |
||||
3. Status: 302 Location: /Account/ExternalLoginCallback?loginProvider=Facebook Set-Cookie: .AspNet.External=<ticket token> External forms middleware does: a. Convert claims identity to ApplicationTicket b. Serialize ApplicationTicket to byte array c. Encrypt and encode byte array to ticket token d. Set cookie to response <—————————- |
||||
4. Get /Account/ExternalLoginCallback?loginProvider=Facebook Cookie: .AspNet.External=<ticket token>
—————————-> |
||||
5. IdentityAuthenticationManager.GetExternalIdentity() The extension method will call into OWIN middleware to explicitly authenticate with external type <—————————- |
||||
6. Authenticate cookie and return user claims identity External forms middleware does: a. Decode and decrypt ticket token into byte array b. Deserialize byte array to ApplicationTicket c. Get claims identity from ApplicationTicket d. Return identity back to caller —————————-> |
||||
7. Status: 200 Body: external login page
After getting the external identity, check if the user is already registered. – If no, return external login confirmation page. – If yes, directly log user in (Not included in this flow) <—————————- |
||||
8. POST /Account/ExternalLoginConfirmation Cookie: .AspNet.External=<ticket token> Body: UserName=test&LoginProvider=Facebook —————————-> |
||||
9. IdentityAuthenticationManager.GetExternalIdentity() The extension method will call into OWIN middleware to explicitly authenticate with external type <—————————- |
||||
10. Authenticate cookie and return user claims identity —————————-> |
||||
11. Status: 302 Location: / Web app code does: a. Create local user via membership provider b. Associate local user with external identity’s ID claim (facebook id) c. Sign the external identity in as Application type d. Redirect to returnUrl or home page <—————————- |
||||
12. Status: 302 Location: / Set-Cookie: .AspNet.Application=<Ticket Token>
Turn claims identity to ticket token and set cookie in response <—————————- |
Working with new Identity API
IdentityAuthenticationManager wraps everything that you need to work with Application and External sign in cookies.
- publicclassIdentityAuthenticationManager
- {
- public IdentityAuthenticationManager();
- public IdentityAuthenticationManager(IdentityStoreManager storeManager);
- publicstring ClaimsIssuer { get; set; }
- publicstring RoleClaimType { get; set; }
- public IdentityStoreManager StoreManager { get; set; }
- publicstring UserIdClaimType { get; set; }
- publicstring UserNameClaimType { get; set; }
- publicvirtualvoid Challenge(HttpContextBase context, string authenticationType, string redirectUrl);
- publicvirtual Task<bool> CheckPasswordAndSignIn(HttpContextBase context, string userName, string password, bool isPersistent);
- publicvirtual Task<bool> CreateAndSignInExternalUser(HttpContextBase context, string loginProvider, IUser user);
- publicvirtual IEnumerable<Microsoft.Owin.Security.AuthenticationDescription> GetExternalAuthenticationTypes(HttpContextBase context);
- publicvirtual Task<ClaimsIdentity> GetExternalIdentity(HttpContextBase context);
- publicvirtual Task<IList<Claim>> GetUserIdentityClaims(string userId, IEnumerable<Claim> claims);
- publicvirtual Task<bool> LinkExternalIdentity(ClaimsIdentity id, string userId, string loginProvider);
- publicvirtual Task SignIn(HttpContextBase context, string userId, bool isPersistent);
- publicvirtual Task SignIn(HttpContextBase context, string userId, IEnumerable<Claim> claims, bool isPersistent);
- publicvirtual Task<bool> SignInExternalIdentity(HttpContextBase context, ClaimsIdentity id, string loginProvider);
- publicvirtualvoid SignOut(HttpContextBase context);
- publicvirtualbool VerifyExternalIdentity(ClaimsIdentity id, string loginProvider);
- }
Method |
Description |
CheckPasswordAndSignIn |
Verify user name and password against storage like SQL server and sign in with Application cookie |
CreateAndSignInExternalUser |
Create user based on external identity from External cookie in storage like SQL server, and sign in user as Application cookie |
GetExternalIdentity |
Get external identity from External cookie |
GetUserIdentityClaims |
Replace user id and name claims and add roles and user custom claims from storage. |
LinkExternalIdentity |
Link external identity with local user in storage |
SignIn |
Sign out External cookie and sign in Application cookie |
SignInExternalIdentity |
Get user associating with external identity in storage and sign this user in as Application cookie |
SignOut |
Sign out from Application cookie |
VerifyExternalIdentity |
Verify if the external identity has the same issuer as loginProvider |
Challenge |
Explicitly ask authentication middleware to send challenge to the response. For example, Application forms middleware will challenge to redirect to login page with 302 status code. |
GetExternalAuthenticationTypes |
Get supported external authentication types which you register in the OWIN middleware pipeline, like Facebook, Google, etc. |
The following shows the login code for the ASP.NET MVC template:
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- publicasyncTask<ActionResult> Login(LoginViewModel model, string returnUrl)
- {
- if (ModelState.IsValid)
- {
- // Validate the user password
- if (await AuthenticationManager.CheckPasswordAndSignIn(HttpContext, model.UserName, model.Password, model.RememberMe))
- {
- return RedirectToLocal(returnUrl);
- }
- }
- // If we got this far, something failed, redisplay form
- ModelState.AddModelError(“”, “The user name or password provided is incorrect.”);
- return View(model);
- }
What about defining custom values to store? Should we store them using claims and you'll handle the serialization? Up to now, we've implemented a custom ticket, can you show or link us to an example with custom fields storing? and using some of the user data? (for instance, I'd like to store the signed user first name and privileges mask, should I use claims for that and you'll take care of the right serialization logic?)
@Yahav, sure, you can use claims to save custom data. Forms auth middleware will serialize and deserialize it back to claims. Claim support both primitive and non-primitive types, please check out: msdn.microsoft.com/…/ms734687.aspx for more information.
I am new user of MVC5, i do it well in MVC3, before commenting i go for post and comment both, i also think about
custom data, but got solution
I am new user of MVC5, i do it well in MVC3, before commenting i go for post and comment both, i also think about
custom data, but got solution
I implemented a custom ticket, can you show or link us to an example with custom fields storing? and using some of the user data@ http://onaprsc.com.vn
Looks like the IdentityAuthenticationManager shouldn't belong to the Microsoft.AspNet.Identity.EntityFramework assembly. Tried to implement a custom store to use Azure Table, having to depend on an assembly with the name EntityFramework in it doesn't feel right.
Can anybody post a full example of how to implement this with a already existing Users database using Int32 for user id.
I have not been able to customize this, and have posted my issue to StackOverflow. Please consider posting a solution if you're experienced with this. I've really simplified my code to narrow it down to my issue (CreateLocalUser fails and returns a false).
stackoverflow.com/…/custom-membership-with-microsoft-aspnet-identity-createlocaluser-fails
-Vito
Is there any way to configure the LightSwitch HTML client to use OWIN?
@Ed, you are right. We covered IdentityAuthenticationManager to extension methods on IdentityStoreManagerBase in new release and moved most of the code in IdentityStoreManager to IdentityStoreManagerBase which is in Microsoft.AspNet.Identity.Core. If you want to implement a Azure table store, you can inherit from the base store manager class and reuse the extension methods on it to sign in and sign out user.
@Martin, we have bunch of changes in identity API in upcoming release. We are going to write a blog about how to migrate existing database to new identity DB. User id as string is part of the design in identity API, which can't be changed to int. However, you can convert your int id to string id during migration.
@Vito, replied in SO.
@Hongye any ETA on the new update / blog post for migration?
@Martin, it will happens right after the next version of VS get released
Shouldn't IdentityAuthenticationManager be behind an IIdentityAuthenticationManager interface? I use Azure Table Storage to persist user details. I want to control where the user data is stored, and not have to use a DB that EntityFramework understands.
Can someone from the team look into this bug: connect.microsoft.com/…/new-webform-app-login-crashes-no-owin-environment ?
Thank you.
@graycrow, thanks for reporting this issue. We are already aware of this issue and fixed it in the next release of Visual Studio. The issue is also mentioned in the preview refresh release note with workaround:
http://www.asp.net/…/release-notes
When creating MVC, SPA and Web Form project with special characters in the project name (e.g. space, leading digits, etc.), the project might not work correctly. You will see error like: "No OWIN environment is available for the request" from server.
This is because OWIN uses "<Assembly Name>.Startup" name to find the app start up class. However, since namespace doesn’t support special characters like space, Visual Studio will convert them to underscore in namespace. For example, project name "Project A" will be converted to "Project_A" in namespace. So OWIN is unable to find the correct startup class.
To workaround, Add <add key="owin:AppStartup" value="<Namespace>.Startup, <Assembly name>"/> under <configuration> <appsettings> section. For example: <add key="owin:AppStartup" value="Project_A.Startup, Project A"/>
I can successfully add users to the "Users" and "UserLogins" tables.
Question:
How do you put users into Roles. I have tried
Roles.AddUserToRole("username","rolename"); It fails all the time.
AuthenticationManager is gone? How do we authenticate users via username/pass now with the latest RC1 Katana/ASP.NET Identity packages?
Does anyone know if OWIN Forms Authentication allows to disable the redirect to login page?
Thanks!
What about defining custom values to store? Should we store them using claims and you'll handle the serialization? Up to now, we've implemented a custom ticket, can you show or link us to an example with custom fields storing? and using some of the user data? (for instance, I'd like to store the signed user first name and privileges mask, should I use claims for that and you'll take care of the right serialization logic?)
Thank you for all and I have exteded methods on IdentityStoreManagerBase in new release and moved most of the code in IdentityStoreManager to IdentityStoreManagerBase. So it helps more my web: http://onaplioa.com.vn
I wonder that my blogs http://www.mayduavongtudong.vn can use this tool, can't it ?
Can my website http://www.mayduavongtudong.vn use this tool ?
It is Great !
I will use it for my website http://www.onaplioachinhhang.com
How to make the Katana/Owin work with windows auth in mvc 5
Thanks a lot, i'll try it tomorrow with my site : http://www.facebook.com/phanphoistanda
I find this article to still be a very useful reference and the page rank is very high for 'mvc owin'. It needs a little bit of updating (e.g., app.UseFormsAuthentication –> app.UseCookieAuthentication)
Good stuff! We recently had to upgrade from Forms Auth to Identity 2.0 for an MVC5 app @ https://sftool.gov. If anyone is interested, check out sftool.blogspot.com/…/upgrade-to-aspnet-identity.html for more info.
Updated url is https://dcdevs.blogspot.com/2017/06/upgrade-to-aspnet-identity.html
I am new user of MVC5. I want to control where the user data is stored. I have not been able to customize this, and have posted my issue to StackOverflow@ http://lioa.net
Thank for all. I am happy to be here so that It help for me in my work. I'd like to store the signed user first name and privileges mask, should I use claims for that.
When creating MVC, SPA and Web Form project with special
I implemented a custom ticket, can you show or link us to an example with custom fields storing? and using some of the user data@ http://www.onaplioanhatlinh.com
What about defining custom values to store? Should web
I implemented a custom ticket, can you show or link us to an example with custom fields storing
uses an application ticket that represents user's identity and keeps it inside user agent's cookie. When user first accesses a resource requiring authorization, it will redirect user to login page. After the user provides credentials, your application code will validate the user name and password and build user claims including user's name, roles, etc. After passing claims to the Forms authentication middleware, it will convert it to an application ticket and serialize, encrypt and encode it into a ticket token. Then, send it out as a cookie. When the next time user sends request with the cookie, the middleware will validate it and convert the ticket token back to claims
Does anyone know how to get this to work with eBay? I want users to authenticate with their eBay credentials. Please help!
Good day very nice blog!! Man .. Beautiful .. Amazing .. I will bookmark your site and take the feeds additionally?I am glad to find numerous useful info right here within the post, we want work out extra techniques in this regard, thank you for sharing
Which is the flow using Federation?
I have this code:
private static void ConfigureAuthForADFS(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = “PortalCRM”,
ExpireTimeSpan = TimeSpan.FromMinutes(20),
SlidingExpiration = true,
//LoginPath = new Microsoft.Owin.PathString(“/AccesoAlPortal/LoginMediadores.aspx”),
Provider = new CookieAuthenticationProvider()
{ …
}
});
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
// This makes any middleware defined above this line run before the Authorization rule is applied in web.config
app.UseStageMarker(PipelineStage.Authenticate);