I'm trying to do authentication with Bearer tokens and owin.
I can issue the token fine using the grant type password
and overriding GrantResourceOwnerCredentials
in AuthorizationServerProvider.cs.
But I can't reach a controller method with the Authorize
attribute.
Here's my code:
Startup.cs
public class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
// normal
public Startup() : this(false) { }
// testing
public Startup(bool isDev)
{
// add settings
Settings.Configure(isDev);
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new AuthorizationServerProvider()
};
}
public void Configuration(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);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.CreatePerOwinContext<LoanManager>(BaseManager.Create);
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
// token generation
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AuthenticationType = "Bearer",
AuthenticationMode = AuthenticationMode.Active
});
}
}
AuthorizationServerProvider.cs
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
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[] { "*" });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
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"));
context.Validated(identity);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// enable CORS for all hosts, headers and methods
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.Routes.MapHttpRoute(
name: "optional params",
routeTemplate: "api/{controller}"
);
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// stop cookie auth
config.SuppressDefaultHostAuthentication();
// add token bearer auth
config.Filters.Add(new MyAuthenticationFilter());
//config.Filters.Add(new HostAuthenticationFilter(Startup.OAuthOptions.AuthenticationType));
config.Filters.Add(new ValidateModelAttribute());
if (Settings.IsDev == false)
{
config.Filters.Add(new AuthorizeAttribute());
}
// make properties on model camelCased
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
MyAuthenticationFilter.cs Custom filter used for debugging purposes
public class MyAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
if (context.Principal != null && context.Principal.Identity.IsAuthenticated)
{
}
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
If I debug AuthenticateAsync
in MyAuthenticationFilter.cs I see the header in the request:
Authorization: Bearer AQAAANCMnd8BFdERjHoAwE_Cl...
But the Identity Claims are empty and context.Principal.Identity.IsAuthenticated
is false.
Any ideas?
Bearer tokens enable requests to authenticate using an access key, such as a JSON Web Token (JWT). The token is a text string, included in the request header. In the request Authorization tab, select Bearer Token from the Type dropdown list. In the Token field, enter your API key value.
The most common way of accessing OAuth 2.0 APIs is using a “Bearer Token”. This is a single string which acts as the authentication of the API request, sent in an HTTP “Authorization” header.
I was looking for the same solution, I spent a week or so on this and I left it. Today I started to search again, I found your questions and I was hoping to find an answer.
So I spent the whole day doing nothing other than trying all the possible solutions, merging suggestions with each other, I found some solution but they were long workarounds, to make the long story short here is what I found.
First of all if you need to authenticate the Web site with a custom third party identity provider token you need to have them both using the same machineKey or you need to have them both on the same server.
You need to add the machineKey to the system.web
section as following:
Web.Config
<system.web>
<authentication mode="None" />
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<machineKey validationKey="*****" decryptionKey="***" validation="SHA1" decryption="AES" />
</system.web>
Here is a link to generate a new machineKey :
Now you need to move to the Startup.Auth.cs file where you can find the Startup.cs partial class, you need to define the OAuthBearerOptions
Startup.Auth.cs
public partial class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
...
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
...
}
}
Replace your Login action inside AccountController with the following:
AccountController.cs
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
/*This will depend totally on how you will get access to the identity provider and get your token, this is just a sample of how it would be done*/
/*Get Access Token Start*/
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://youridentityproviderbaseurl");
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("UserName", model.Email));
postData.Add(new KeyValuePair<string, string>("Password", model.Password));
HttpContent content = new FormUrlEncodedContent(postData);
HttpResponseMessage response = await httpClient.PostAsync("yourloginapi", content);
response.EnsureSuccessStatusCode();
string AccessToken = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync());
/*Get Access Token End*/
If(!string.IsNullOrEmpty(AccessToken))
{
var ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(AccessToken);
var id = new ClaimsIdentity(ticket.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, id);
return RedirectToLocal(returnUrl);
}
ModelState.AddModelError("Error", "Invalid Authentication");
return View();
}
The last thing you need to do is to place this line of code in the Global.asax.cs to avoid Anti Forgery exceptions:
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
…
}
}
Hope this would work for you.
A year since this was posted, and I too experienced the same problem.
As you can see, my bearer token is recognized in the request headers, but my identity is still not be authenticated.
To fix, the short answer is make sure to configure your OAuth middleware before you configure your WebApi middleware (HttpConfiguration).
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