Situation: I have a Web API 2 project which acts as an Authorization server (/token endpoint) and a resource server. I am using the template that comes out of box with ASP.Net Web API minus any MVC reference. The Start.Auth is configured as below:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.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
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = ConfigurationManager.AppSettings["Test_Facebook_AppId"],
AppSecret = ConfigurationManager.AppSettings["Test_Facebook_AppSecret"],
//SendAppSecretProof = true,
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(0);
}
}
};
facebookAuthenticationOptions.Scope.Add("email user_about_me user_location");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
}
The MVC 5 Client (different Project) uses the Web API app for authorization and data. Below is the code to retrieve the Bearer token in case of Username/Password store:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
model.ExternalProviders = await GetExternalLogins(returnUrl);
return View(model);
}
var client = Client.GetClient();
var response = await client.PostAsync("Token",
new StringContent(string.Format("grant_type=password&username={0}&password={1}", model.Email, model.Password), Encoding.UTF8));
if (response.IsSuccessStatusCode)
{
return RedirectToLocal(returnUrl);
}
return View();
}
Problem
I could retrieve the Bearer token and then add it to the Authorization Header for subsequent calls. I think that would be ok in case of an Angular App or a SPA. But I think there should be something in MVC that handles it for me, like automatically store it in a cookie and send the cookie on subsequent requests. I have searched around quite a lot and there are posts which hint towards this (Registering Web API 2 external logins from multiple API clients with OWIN Identity) but I haven't been able to figure out what to do after I get a token.
Do I need to add something in the MVC app Startup.Auth?
Ideally, I need the functionality which the AccountController in ASP.Net Template (MVC + Web API) gives out of box (Logins, Register, External logins, forget password etc etc...) but with the MVC and Web API in different projects.
Is there a template or a git repo which has this boiler plate code?
Thanks in advance!
Update Incorporating @FrancisDucharme suggestions, below is the code for GrantResourceOwnerCredentials().
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
//Add a response cookie...
context.Response.Cookies.Append("Token", context.Options.AccessTokenFormat.Protect(ticket));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
But I can't still seem to get that Cookie or figure out what to do next.
Restating Questions:
Should you keep tokens in cookies or in local storage? There are two patterns for client-side storage of bearer tokens: cookies and using HTML5 local storage. If cookies are being used to transmit the bearer token from client to server, then cookies would also be used to store the bearer token on the client side.
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.
You could have your Startup class return a response cookie that the client will then return on all subsequent requests, here's an example. I would do it in GrantResourceOwnerCredentials
.
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//your authentication logic here, if it fails, do this...
//context.SetError("invalid_grant", "The user name or password is incorrect.");
//return;
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
AuthenticationTicket ticket = new AuthenticationTicket(identity);
//Add a response cookie...
context.Response.Cookies.Append("Token", context.Options.AccessTokenFormat.Protect(ticket));
context.Validated(ticket);
}
The Startup class:
public partial class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public Startup()
{
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
}
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
//I use CORS in my projects....
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
WebApiConfig.Register(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true, //I have this here for testing purpose, production should always only accept HTTPS encrypted traffic.
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}
}
That assumes the client has cookies enabled, of course.
Then, modify your MVC headers to add the Authorization header to all requests as such.
In the ActionFilterAttribute
, fetch your cookie value (Token
) and add the header.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With