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);
- }
0 comments