Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combine the use of authentication both for MVC pages and for Web API pages?

Tags:

I have an MVC 5 web application and can login with a Login.cshtml page and get a cookie and the login works fine. But, I would like to do a login with the Web API and then (maybe) set a cookie so that I am logged in for my MVC pages... (or login with the MVC login and then access the Web API) however the web api returns a bearer token and not a cookie token... so this doesn't work. Is there a way to combine the use of authentication both for my MVC pages and for my Web API pages?

UPDATE:

This isn't really a code issue, more of a conceptual issue.

Normal MVC web pages examine a cookie named, by default, ".AspNet.ApplicationCookie" to determine the requesters identity. This cookie is generated by calling ApplicationSignInManager.PasswordSignInAsync.

WebAPI calls, on the other hand, examine the requests headers for an item named Authorization... and uses that value to determine the requesters identity. This is returned from a WebAPI call to "/Token".

These are very different values. My website needs to use both MVC pages and WebAPI calls (to dynamically update those pages)... and both need to be authenticated to perform their tasks.

The only method I can think of is to actually authenticate twice... once with a WebAPI call and again with the Login post. (see my Answer below).

This seems very hacky... but I don't understand the authorization code enough to know if there is a more proper way of accomplishing this.

like image 470
Brian Rice Avatar asked Aug 22 '15 22:08

Brian Rice


People also ask

Are MVC and Web API merged into one in mvc6?

ASP.NET MVC 6 comes with some new features as well. Some prominent ones are: - MVC, WEB API and Web Pages are merged into one single framework.

Which authentication is used in MVC?

The Authentication is performed by IIS in one of three ways such as basic, digest, or Integrated Windows Authentication. When IIS authentication is completed, then ASP.NET uses the authenticated identity to authorize access.

What is difference in authentication in ASP.NET MVC and Web API?

1. Asp.Net MVC is used to create web applications that returns both views and data but Asp.Net Web API is used to create full blown HTTP services with easy and simple way that returns only data not view. 2.


2 Answers

The best way to achieve this, is to have an authorization server (a Web API generating a token) and token consumption middle-ware in your MVC project. IdentityServer should help. However I have done it like this:

I built an authorization server using JWT with Web API and ASP.Net Identity as explained here.

Once you do that, your Web APIs startup.cs will look like this:

 // Configures cookie auth for web apps and JWT for SPA,Mobile apps  private void ConfigureOAuthTokenGeneration(IAppBuilder app)  {     // Configure the db context, user manager and role manager to use a single instance per request     app.CreatePerOwinContext(ApplicationDbContext.Create);     app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);     app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);      // Cookie for old school MVC application     var cookieOptions = new CookieAuthenticationOptions     {         AuthenticationMode = AuthenticationMode.Active,         CookieHttpOnly = true, // JavaScript should use the Bearer         AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,                         LoginPath = new PathString("/api/Account/Login"),         CookieName = "AuthCookie"     };     // Plugin the OAuth bearer JSON Web Token tokens generation and Consumption will be here     app.UseCookieAuthentication(cookieOptions);      OAuthServerOptions = new OAuthAuthorizationServerOptions()     {         //For Dev enviroment only (on production should be AllowInsecureHttp = false)         AllowInsecureHttp = true,         TokenEndpointPath = new PathString("/oauth/token"),         AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),         Provider = new CustomOAuthProvider(),                         AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["JWTPath"])     };      // OAuth 2.0 Bearer Access Token Generation     app.UseOAuthAuthorizationServer(OAuthServerOptions); } 

You can find the CustomOAuthProvider and CustomJwtFormat classes here.

I wrote a consumption logic (i.e. middleware) in all my other APIs (Resource servers) that I wanted to secure using the same token. Since you want to consume the token generated by the Web API in your MVC project, after implementing the authorization server, you need to the following:

In your MVC app, add this in startup.cs:

public void Configuration(IAppBuilder app) {         ConfigureOAuthTokenConsumption(app); }  private void ConfigureOAuthTokenConsumption(IAppBuilder app) {     var issuer = ConfigurationManager.AppSettings["AuthIssuer"];     string audienceid = ConfigurationManager.AppSettings["AudienceId"];     byte[] audiencesecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);      app.UseCookieAuthentication(new CookieAuthenticationOptions { CookieName = "AuthCookie" , AuthenticationType=DefaultAuthenticationTypes.ApplicationCookie });      //// Api controllers with an [Authorize] attribute will be validated with JWT     app.UseJwtBearerAuthentication(         new JwtBearerAuthenticationOptions         {             AuthenticationMode = AuthenticationMode.Passive,             AuthenticationType = "JWT",             AllowedAudiences = new[] { audienceid },             IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]             {                 new SymmetricKeyIssuerSecurityTokenProvider(issuer, audiencesecret)                                        }          }); } 

In your MVC controller, when you receive the token, de-serialize it and generate a cookie from the access token:

AccessClaims claimsToken = new AccessClaims(); claimsToken = JsonConvert.DeserializeObject<AccessClaims>(response.Content); claimsToken.Cookie = response.Cookies[0].Value;                Request.Headers.Add("Authorization", "bearer " + claimsToken.access_token); var ctx = Request.GetOwinContext(); var authenticateResult = await ctx.Authentication.AuthenticateAsync("JWT"); ctx.Authentication.SignOut("JWT"); var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie); ctx.Authentication.SignIn(applicationCookieIdentity); 

Generate a machine key and add it in web.config of your Web API and ASP.Net MVC site.

With this, a cookie will be created and the [Authorize] attribute in the MVC site and the Web API will honor this cookie.

P.S. I have done this with a Web API issuing JWT (Authorization server or Auth & resource server) and was able to consume it in an ASP.Net MVC website, SPA Site built in Angular, secure APIs built in python (resource server), spring (resource server) and an Android App.

like image 190
Ravi A. Avatar answered Oct 25 '22 16:10

Ravi A.


Ugg... what I had to do was use the Login.cshtml form and override the submit... make an Ajax call to get the WebApi bearer token... and then do the form submit to get the actual MVC cookie. So, I'm actually making two login requests... one for the WebApi token and the other for the MVC cookie.

Seem pretty hacky to me... it would be nice if there was some way to sign in to MVC using the bearer token... or a call to the WebApi that would return me a cookie that I can use for normal MVC page requests.

If anyone has a better way I would love to hear it.

This is script code that I added to Login.cshtml:

    $(document).ready(function () {         $('form:first').submit(function (e) {             e.preventDefault();             var $form = $(this);             var formData = $form.serializeObject(); // https://github.com/macek/jquery-serialize-object             formData.grant_type = "password";             $.ajax({                 type: "POST",                 url: '@Url.Content("~/Token")',                 dataType: "json",                 data: formData, // seems like the data must be in json format                 success: function (data) {                     sessionStorage.setItem('token', data.access_token);                     $form.get(0).submit(); // do the actual page post now                 },                 error: function (textStatus, errorThrown) {                 }             });         });     }); 
like image 28
Brian Rice Avatar answered Oct 25 '22 16:10

Brian Rice