I have spent the last week creating an API for an existing MVC application, and am now attempting to secure the API along with reworking the MVC side security as needed.
Currently, the MVC application is set up to use an application cookie via OWIN/OAuth/Identity. I have attempted to incorporate the Bearer token that the Web API is set up to generate whenever making calls to restricted API methods, but have had little success so far - GET requests work just fine, but POST requests are losing the Authorization header when received by the API.
I have created an SDK Client that is being used by the MVC app to make the calls to the API, and have tried a total of three methods of setting the Authorization header for any given call to the API, all of which seem to work just fine for GET requests, but fail completely for any POST requests I need to make...
I can set the Request header in the MVC controller:
HttpContext.Request.Headers.Add("Authorization", "Bearer " + response.AccessToken);
(where response.AccessToken is the token previously retrieved from the API)
I can set the Request header via an extension method on the SDK Client:
_apiclient.SetBearerAuthentication(token.AccessToken)
or I can set the Request header manually on the SDK Client:
_apiClient.Authentication = new AuthenticationHeaderValue("Bearer, accessToken);
(Where accessToken is the token retrieved previously, passed to the Client method being called).
I have very little to go on from this point as to what is causing the issue. The only thing I have been able to glean so far is that ASP.NET causes all POST requests to first send in a request with an Expect header for an HTTP 100-Continue response, after which it will finish the actual POST request. However, it seems that when it does this second request, the Authorization header is no longer present and so the API's Authorize attribute will cause a 401-Unauthorized response instead of actually running the API method.
So, how do I take the Bearer token that I am able to retrieve from the API, and use it on subsequent requests, including the various POST requests that I will need to make?
Beyond that, what is the best way of storing this token on the MVC application itself? I would rather like to avoid having to pass around the string to every method in the application that could need it, but I also have been reading that storing it in a cookie is a very bad idea for security reasons.
A few further points that will be of interest immediately after I get passed this issue:
Does using OAuth Bearer Tokens mean that I can no longer use ApplicationCookies for the MVC application? And/or will it render the following code useless throughout the application?
User.Identity.GetUserId()
Currently I am forced into commenting out my API [Authorize] attributes in order to continue with my work, which obviously isn't ideal but it does allow me to get on with things temporarily.
Startup files:
MVC:
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
private void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ADUIdentityDbContext.Create);
app.CreatePerOwinContext<ADUUserManager>(ADUUserManager.Create);
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
//This should be set to FALSE before we move to production.
AllowInsecureHttp = true,
ApplicationCanDisplayErrors = true,
TokenEndpointPath = new PathString("/api/token"),
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ExternalBearer,
CookieName = "ADU",
ExpireTimeSpan = TimeSpan.FromHours(2),
LoginPath = new PathString("/Account/Login"),
SlidingExpiration = true,
});
}
}
API
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.DependencyResolver = new NinjectResolver(new Ninject.Web.Common.Bootstrapper().Kernel);
WebApiConfig.Register(config);
ConfigureOAuth(app);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ADUIdentityDbContext.Create);
app.CreatePerOwinContext<ADUUserManager>(ADUUserManager.Create);
OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
};
//token generation
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
private IUserBusinessLogic _userBusinessLogic;
/// <summary>
/// Creates the objects necessary to initialize the user business logic field and initializes it, as this cannot be done by dependency injection in this case.
/// </summary>
public void CreateBusinessLogic()
{
IUserRepository userRepo = new UserRepository();
IGeneratedExamRepository examRepo = new GeneratedExamRepository();
IGeneratedExamBusinessLogic examBLL = new GeneratedExamBusinessLogic(examRepo);
_userBusinessLogic = new UserBusinessLogic(userRepo, examBLL);
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); }
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
//create a claim for the user
ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", user.Id));
context.Validated(identity);
}
}