I am using Asp.net Webform project with Web API. I configured JWT token-based authentication and now I want to customize the authentication response
Here are my configurations,
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
// Web API routes
config.MapHttpAttributeRoutes();
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
ConfigureOAuth(app);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
WebApiConfig.Register(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
String apiHttpOnly = ConfigurationManager.AppSettings["AllowInsecureHttp"];
String tokenTimeSpan = ConfigurationManager.AppSettings["tokenTimeSpanFromMinutes"];
bool allowInsecureHttp = !String.IsNullOrEmpty(apiHttpOnly) ?
Convert.ToBoolean(apiHttpOnly) : false;
int accessTokenExpireTimeSpan = !String.IsNullOrEmpty(tokenTimeSpan) ?
Convert.ToInt32(tokenTimeSpan) : 60;
var authProvider = new AuthorizationServiceProvider();
OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = allowInsecureHttp,
TokenEndpointPath = new PathString("/api/authenticate"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(accessTokenExpireTimeSpan),
Provider = authProvider
};
app.UseOAuthAuthorizationServer(options);
}
}
AuthorizationServiceProvider
public class AuthorizationServiceProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return base.ValidateClientAuthentication(context);
}
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
if (Membership.ValidateUser(context.UserName, context.Password))
{
identity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
identity.AddClaim(new Claim("username", context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Name, "admin admin"));
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "Provide username and password is incorrect.");
}
return base.GrantResourceOwnerCredentials(context);
}
}
When I call the API with the right credentials, it returns like
{
"access_token": "uEwmXl6N0mJXVUZesxA_2tG5lIuZUIUDaxtjAl0QGE6j2-J7n4c63zboOUClGjRQf1IDY9-nBgyq0HP5WR7MMxTYoHGIyiHIbcKu9AYwhECCGaVBCxY2Ounhit4N1pYK1vV6uX6AcoA-a0xhytF8Jz27D77ZvCLi3PuUQDEXSp0pkGG796wu1fRZCaRsCB-kLoa-_V7KJaGGhhoybN_c0GNOBhhwmGpx6Js26-Vx-lmWpfsPUE1aKrJfx-oMcyE5x7CooAlx4vA6iZhnNfmYdRejRKoKKnObyuAsym7mVdZY3bpv",
"token_type": "bearer",
"expires_in": 5183999
}
I want to customize the response by adding some extra attributes like,
{
"access_token": "uEwmXl6N0mJXVUZesxA_2tG5lIuZUIUDaxtjAl0QGE6j2-J7n4c63zboOUClGjRQf1IDY9-nBgyq0HP5WR7MMxTYoHGIyiHIbcKu9AYwhECCGaVBCxY2Ounhit4N1pYK1vV6uX6AcoA-a0xhytF8Jz27D77ZvCLi3PuUQDEXSp0pkGG796wu1fRZCaRsCB-kLoa-_V7KJaGGhhoybN_c0GNOBhhwmGpx6Js26-Vx-lmWpfsPUE1aKrJfx-oMcyE5x7CooAlx4vA6iZhnNfmYdRejRKoKKnObyuAsym7mVdZY3bpv",
"token_type": "bearer",
"expires_in": 5183999,
"attribute1" : "abc",
"attribute2" : "ert"
}
Anyone have an idea to do that?
Finally, I investigate the problem and find out the solution. I posted it here, maybe it may help someone.
Add AuthenticationProperties
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"attribute1" , "abc"
},
{
"attribute2" , "ert"
}
});
Then create Ticket using AuthenticationTicket
var ticket = new AuthenticationTicket(identity, props);
Add ticket to the context
context.Validated(ticket);
Then implement the override method TokenEndpoint
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
Finally AuthorizationServiceProvider class be like,
public class AuthorizationServiceProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return base.ValidateClientAuthentication(context);
}
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
if (string.IsNullOrEmpty(context.UserName) || string.IsNullOrEmpty(context.Password))
{
context.SetError("invalid_request", "No username or password are provided.");
}
else if (Membership.ValidateUser(context.UserName, context.Password))
{
identity.AddClaim(new Claim("username", context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
/*
* All custom response props are define here.
* --------usage----------
* use as dictionary
* */
var props = new AuthenticationProperties(new Dictionary<string, string>
{
//{
// "test" , "val"
//}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
else
{
context.SetError("invalid_grant", "Provide username and password is incorrect.");
}
return base.GrantResourceOwnerCredentials(context);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
}
For more info - https://stackoverflow.com/a/26369622/8403632
Sample Example - https://github.com/Leftyx/OwinWebApiBearerToken
Related
I have an ASP.Net Web API 2 with BasicAuthenticationAttribute that is working as expected. In my application, there are different controllers and I want to add bearer token-based authentication to one of my controllers. I added those NuGet packages:
Microsoft.AspNet.WebApi.Owin
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security.OAuth
Here is the WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling =
DateTimeZoneHandling.Local;
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"DefaultApi",
"api/{controller}/{id}",
new {id = RouteParameter.Optional}
);
config.MessageHandlers.Add(new RequestResponseHandler());
config.Filters.Add(new CustomExceptionFilter());
var resolver = config.DependencyResolver; //Assuming one is set.
var basicAuth = (BasicAuthenticationAttribute)resolver.GetService(typeof(BasicAuthenticationAttribute));
// Web API configuration and services
if (basicAuth != null) config.Filters.Add(basicAuth);
}
}
Here is the Owin Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var configuration = GlobalConfiguration.Configuration;
WebApiConfig.Register(configuration);
app.UseWebApi(configuration);
Configure(app);
}
private static void Configure(IAppBuilder app)
{
var options = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new Microsoft.Owin.PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
AllowInsecureHttp = true,
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Here is the controller
[RoutePrefix("api/v2/game/abc101")]
public class A101Controller : ApiController
{
private readonly IGameServicesABC101 _gameServices;
private readonly IMapper _mapper;
public A101Controller(IGameServicesABC101 gameServices, IMapper mapper)
{
_gameServices = gameServices;
_mapper = mapper;
}
[HttpPost]
[Authorize]
[Route("purchase")]
public async Task<IHttpActionResult> PurchaseGame(RequestDto game)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
...
Basic Authentication Attribute
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
private const string Realm = "My Realm";
private readonly Func<IUserValidate> _factory;
public BasicAuthenticationAttribute(Func<IUserValidate> factory)
{
_factory = factory;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization == null)
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
if (actionContext.Response.StatusCode == HttpStatusCode.Unauthorized)
actionContext.Response.Headers.Add("WWW-Authenticate",
$"Basic realm=\"{Realm}\"");
}
else
{
var authenticationToken = actionContext.Request.Headers
.Authorization.Parameter;
try
{
//Decode the string
var decodedAuthenticationToken = Encoding.UTF8.GetString(
Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var username = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
var uv = _factory();
if (uv.Login(username, password))
{
var identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) HttpContext.Current.User = principal;
}
else
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
}
}
catch
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
}
I am using Unity in my application. Basic Authentication works as expected. When I make a request without a token to ...api/v2/game/abc101/purchase I get a response either. Shouldn't I get 401? What I am missing?
UPDATE
I am searching and trying to find how to use both basic authentication and token-based authentication for different controllers. Here is my status update.
There is no code in the Global.asax
Here is the Owin Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = GlobalConfiguration.Configuration;
WebApiConfig.Register(config);
app.UseWebApi(config);
Configure(app, config.DependencyResolver);
config.EnsureInitialized();
}
private static void Configure(IAppBuilder app, IDependencyResolver resolver)
{
var options = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new Microsoft.Owin.PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
AllowInsecureHttp = true,
Provider = new AuthorizationServerProvider((IUserValidate)resolver.GetService(typeof(IUserValidate)))
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
app.UseOAuthAuthorizationServer(options);
}
}
Here is AuthorizationServerProvider
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
private readonly IUserValidate _userValidate;
public AuthorizationServerProvider(IUserValidate userValidate)
{
_userValidate = userValidate;
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
if (!context.TryGetBasicCredentials(out var clientId, out var clientSecret))
{
context.SetError("Error", "Error...");
}
if (_userValidate.Login(clientId, clientSecret))
{
context.Validated();
}
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Content-Type" });
if (_userValidate.Login(context.UserName, context.Password))
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "admin"));
context.Validated(identity);
}
else
{
context.SetError("Error", "Error...");
}
}
}
The rest is the same as the previous code samples.
When I call ...api/v2/game/abc101/purchase I am getting 401, it is progress. But when I call http://localhost:52908/token I am getting unsupported_grant_type. I am sending requests via Postman and I am sending a POST requests with content-type x-www-form-urlencoded. Grant-Type is password and username/password is also correct.
When I call another controller http://localhost:52908/api/v2/game/purchase basic authentication does NOT work!
Hope someone can help.
UPDATE 1
Now I am getting the token, one step at a time :) How can I also use Basic authentication for another controller?
Here is Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = GlobalConfiguration.Configuration;
Configure(app, config.DependencyResolver);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
config.EnsureInitialized();
}
private static void Configure(IAppBuilder app, IDependencyResolver resolver)
{
var options = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new Microsoft.Owin.PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
Provider = new AuthorizationServerProvider((IUserValidate)resolver.GetService(typeof(IUserValidate)))
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Here is the Authorization Server Provider
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
private readonly IUserValidate _userValidate;
public AuthorizationServerProvider(IUserValidate userValidate)
{
_userValidate = userValidate;
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
if (!context.TryGetBasicCredentials(out var clientId, out var clientSecret))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
}
if (_userValidate.Login(clientId, clientSecret))
{
context.Validated();
}
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (_userValidate.Login(context.UserName, context.Password))
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "admin"));
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
}
}
}
As I mentioned before, I have Basic Authentication Attribute and somehow I have to use it in my other controller.
UPDATE 2
How can I use OverrideAuthentication and my basic authentication attribute?
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
private const string Realm = "My Realm";
private readonly Func<IUserValidate> _factory;
public BasicAuthenticationAttribute(Func<IUserValidate> factory)
{
_factory = factory;
}
...
UPDATE 3
I tried this in my basic authentication attribute OnAuthorization method;
var authentication = DependencyResolver.Current.GetService<IUserValidate>();
if (authentication.Login(username, password))
{
var identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) HttpContext.Current.User = principal;
}
There are 2 problems, authentication is null, and somehow authentication token in the attribute is the bearer authentication username/password even though I use basic authentication username/password in the request. It's very weird!
/Get the authentication token from the request header
var authenticationToken = actionContext.Request.Headers
.Authorization.Parameter;
Any help please?
Thanks in advance.
After long googling, here is how I managed to use both basic authentication and bearer authentication for my different controllers.
In Custom basic authentication Attribute I used dependency and requestScope.GetService.
public class CustomBasicAuthenticationAttribute : AuthorizationFilterAttribute
{
[Dependency] public static IUserValidate authentication { get; set; }
private const string Realm = "My Realm";
public override void OnAuthorization(HttpActionContext actionContext)
{
var requestScope = actionContext.Request.GetDependencyScope();
//If the Authorization header is empty or null
//then return Unauthorized
if (actionContext.Request.Headers.Authorization == null)
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
// If the request was unauthorized, add the WWW-Authenticate header
// to the response which indicates that it require basic authentication
if (actionContext.Response.StatusCode == HttpStatusCode.Unauthorized)
actionContext.Response.Headers.Add("WWW-Authenticate",
$"Basic realm=\"{Realm}\"");
}
else
{
//Get the authentication token from the request header
var authenticationToken = actionContext.Request.Headers
.Authorization.Parameter;
try
{
//Decode the string
var decodedAuthenticationToken = Encoding.UTF8.GetString(
Convert.FromBase64String(authenticationToken));
//Convert the string into an string array
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
//First element of the array is the username
var username = usernamePasswordArray[0];
//Second element of the array is the password
var password = usernamePasswordArray[1];
authentication = requestScope.GetService(typeof(IUserValidate)) as IUserValidate;
if (authentication != null && authentication.Login(username, password))
{
var identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) HttpContext.Current.User = principal;
}
else
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
}
}
catch
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
}
In one of my controller, I added those attributes
[OverrideAuthentication]
[CustomBasicAuthentication]
[HttpPost, Route("purchase")]
public async Task<IHttpActionResult> PurchaseGame(RequestDto game)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
...
Now I can use bearer token authentication for ...api/v2/game/abc101/purchase and basic authentication for ...api/v2/game/purchase.
Update
The vital part is the dependency and actionContext.Request.GetDependencyScope();. Without OverrideAuthentication it is working as expected.
Hope this solution helps for others.
Following Owin OAuth guide implemented the authorization code flow below. Am successfully receiving the authorization code from server, ie. till step D mentioned in the article.
When I request access_token from the server with grant_type=authorization_code. I am getting 400 Bad Request error saying invalid grant
Here's the code:
Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
//use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
AuthorizeEndpointPath = new PathString("/oauth/authorize"),
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(60),
Provider = new SimpleOAuthProvider(),
AuthorizationCodeProvider = new AuthenticationCodeProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
// 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(new GoogleOAuth2AuthenticationOptions()
//{
// ClientId = "",
// ClientSecret = ""
//});
}
AuthorizationCodeProvider
internal class AuthenticationCodeProvider: IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var code = Guid.NewGuid().ToString("n");
using (AuthRepository _repo = new AuthRepository())
{
var token = new AuthCode()
{
Token = HashProvider.GetHash(code),
IssuedUtc = DateTime.UtcNow
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddAuthCodeToken(token);
if (result)
{
context.SetToken(token.Token);
}
}
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
if (headerPresent)
{
context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
}
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
string hashedToken = context.Token;
hashedToken = Uri.UnescapeDataString(hashedToken);
using (AuthRepository _repo = new AuthRepository())
{
var authCode = await _repo.FindAuthCodeToken(hashedToken);
if (authCode != null)
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(authCode.ProtectedTicket);
var result = await _repo.RemoveAuthCodeToken(hashedToken);
}
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
OAuthAuthorizationServerProvider
public class SimpleOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
//Check if client details are valid
string clientId = string.Empty;
string clientSecret = string.Empty;
AuthClient client = null;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (context.ClientId == null)
{
//Remove the comments from the below line context.SetError, and invalidate context
//if you want to force sending clientId/secrects once obtain access tokens.
context.Validated();
//context.SetError("invalid_clientId", "ClientId should be sent.");
return Task.FromResult<object>(null);
}
using (AuthRepository _repo = new AuthRepository())
{
client = _repo.FindClient(context.ClientId);
}
if (client == null)
{
context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
return Task.FromResult<object>(null);
}
if (client.Type == (Int16)ClientTypeEnum.Native)
{
if (string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("invalid_clientId", "Client secret should be sent.");
return Task.FromResult<object>(null);
}
else
{
if (client.Secret != clientSecret)
{
context.SetError("invalid_clientId", "Client secret is invalid.");
return Task.FromResult<object>(null);
}
}
}
if (!client.Active)
{
context.SetError("invalid_clientId", "Client is inactive.");
return Task.FromResult<object>(null);
}
context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
if (headerPresent)
{
context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
}
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
using (AuthRepository _repo = new AuthRepository())
{
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
if (!user.EmailConfirmed)
{
context.SetError("unconfirmed_email", "Please confirm your email id.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim("FullName", user.FullName));
identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
identity.AddClaim(new Claim("sub", context.UserName));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"session_id", Guid.NewGuid().ToString("n")
},
{
"email", user.Email
},
{
"user_id", user.Id.ToString()
},
{
"fullname", user.FullName.ToString()
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["client_id"];
var currentClient = context.ClientId;
if (originalClient != currentClient)
{
context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
return Task.FromResult<object>(null);
}
// Change auth ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
//var newClaim = newIdentity.Claims.Where(c => c.Type == "newClaim").FirstOrDefault();
//if (newClaim != null)
//{
// newIdentity.RemoveClaim(newClaim);
//}
//newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
//Authorization Code flow
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
string clientId = context.ClientId;
AuthClient client = null;
using (AuthRepository _repo = new AuthRepository())
{
client = _repo.FindClient(context.ClientId);
}
if (client == null)
{
context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
return Task.FromResult<object>(null);
}
context.Validated(context.RedirectUri);
return Task.FromResult<object>(null);
}
public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(
context.ClientId, OAuthDefaults.AuthenticationType),
context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
context.Validated(identity);
}
public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
//Generate auth code to be sent to oauth/token endpoint for getting access_token
if (context.AuthorizeRequest.IsImplicitGrantType)
{
var identity = new ClaimsIdentity("Bearer");
context.OwinContext.Authentication.SignIn(identity);
context.RequestCompleted();
}
else if (context.AuthorizeRequest.IsAuthorizationCodeGrantType)
{
var redirectUri = context.Request.Query["redirect_uri"];
var clientId = context.Request.Query["client_id"];
var userId = context.Request.Query["user_id"];
var state = context.Request.Query["state"];
var scope = context.Request.Query["scope"];
var sessionId = Guid.NewGuid().ToString("n");
var identity = new ClaimsIdentity(new GenericIdentity(
clientId, OAuthDefaults.AuthenticationType));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToString()));
var authorizeCodeContext = new AuthenticationTokenCreateContext(
context.OwinContext,
context.Options.AuthorizationCodeFormat,
new AuthenticationTicket(
identity,
new AuthenticationProperties(new Dictionary<string, string>
{
{"client_id", clientId},
{"user_id",userId },
{"session_id", sessionId },
{"redirect_uri", redirectUri}
})
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)
}));
await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);
//Set Cors
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
if (headerPresent)
{
context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
}
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var model = new { RedirectUri = redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token) + "&state=" + state + "&scope=" + scope };
var json = Newtonsoft.Json.JsonConvert.SerializeObject(model);
context.Response.Write(json);
context.RequestCompleted();
}
}
/// <summary>
/// Verify the request for authorization_code
/// </summary>
public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
{
if ((context.AuthorizeRequest.IsAuthorizationCodeGrantType || context.AuthorizeRequest.IsImplicitGrantType))
{
context.Validated();
}
else
{
context.Rejected();
}
}
//public override async Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
//{
// var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
// if (allowedOrigin == null) allowedOrigin = "*";
// var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
// if (headerPresent)
// {
// context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
// }
// context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
// var userIdTemp = context.Ticket.Identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
// if(userIdTemp == null)
// {
// }
// long userId = Convert.ToInt64(userIdTemp.Value);
// var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
// ApplicationUser user = await userManager.FindByIdAsync(userId);
// var identity = new ClaimsIdentity(context.Options.AuthenticationType);
// identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
// identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
// identity.AddClaim(new Claim("FullName", user.FullName));
// identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
// //identity.AddClaim(new Claim("sub", context.UserName));
// var props = new AuthenticationProperties(new Dictionary<string, string>
// {
// //{
// // "client_id", (context.Ticket.Properties.Dictionary.FirstOrDefault.ClientId == null) ? string.Empty : context.ClientId
// //},
// {
// "session_id", Guid.NewGuid().ToString("n")
// },
// {
// "email", user.Email
// },
// {
// "user_id", user.Id.ToString()
// },
// {
// "fullname", user.FullName.ToString()
// }
// });
// var ticket = new AuthenticationTicket(identity, props);
// context.Validated(ticket);
//}
}
It seems the middleware will check if the key redirect_uri exists in the dictionary of AuthenticationProperties, remove it and everything works fine(with validated context).
A simplified example of AuthorizationCodeProvider woubld be like so:
public class AuthorizationCodeProvider:AuthenticationTokenProvider {
public override void Create(AuthenticationTokenCreateContext context) {
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context) {
context.DeserializeTicket(context.Token);
context.Ticket.Properties.Dictionary.Remove("redirect_uri"); // <-
}
}
And don't forget to make the context validated in the overridden method OAuthAuthorizationServerProvider.ValidateClientAuthentication. Again, here's a simplified example which inherit from the ApplicationOAuthProvider class of the template project:
public partial class DefaultOAuthProvider:ApplicationOAuthProvider {
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) {
if(null!=context.RedirectUri) {
context.Validated(context.RedirectUri);
return Task.CompletedTask;
}
return base.ValidateClientRedirectUri(context);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
if(context.TryGetFormCredentials(out String clientId, out String clientSecret)) {
// Specify the actual expected client id and secret in your case
if(("expected-clientId"==clientId)&&("expected-clientSecret"==clientSecret)) {
context.Validated(); // <-
return Task.CompletedTask;
}
}
return base.ValidateClientAuthentication(context);
}
public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
}
}
Note that if you invoke context.Validated with a particular client id, then you will have to put the same client_id in the properties of the ticket, you can do that with the method AuthenticationTokenProvider.Receive
I have followed this tutorial Create a RESTful API with authentication using Web API and Jwt
I managed to get the authentication part working but the authorization part is not working(towards end of tutorial). If I add the jwt token with the word bearer in the authorization header it gives me 401 authorization denied.
I'm thinking maybe I need to create a custom authorization attribute.
Is there any way to use the existing authorize attribute?
What does the existing Authorize attribute look for in order to authorize a user(not including roles or users parameters in the authorize attribute) ?
Startup.Auth.cs
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["Issuer"];
var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["Secret"]);
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { "Any" },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(issuer)
});
}
}
CustomOAuthProvider
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var blSecurity = new BLSecurity();
var user = blSecurity.LogonUser(context.UserName, context.Password);
if (!(user.ResponseType == Global.Response.ResponseTypes.Success))
{
context.SetError("Authentication Error", "The user name or password is incorrect");
return Task.FromResult<object>(null);
}
var ticket = new AuthenticationTicket(SetClaimsIdentity(context, user.LoggedOnUser), new AuthenticationProperties());
context.Validated(ticket);
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
private static ClaimsIdentity SetClaimsIdentity(OAuthGrantResourceOwnerCredentialsContext context, User user)
{
//Add User Claims
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("bn", user.BranchName));
identity.AddClaim(new Claim("fn", user.FirstName));
identity.AddClaim(new Claim("ln", user.LastName));
//Add User Role Claims
var blRole = new BLRole();
var roles = blRole.GetRolesByUserId(user.UserID);
if (roles != null && roles.Count > 0)
{
foreach (var role in roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role.RoleName));
}
}
return identity;
}
}
CustomJwtFormat
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private static readonly byte[] _secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]);
private readonly string _issuer;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
var signingKey = new HmacSigningCredentials(_secret);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(_issuer, null, data.Identity.Claims, issued.Value.UtcDateTime.ToLocalTime(), expires.Value.UtcDateTime.ToLocalTime(), signingKey));
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
I managed to solve it by applying what a guy posted in the comments section. see below quoted.
Roman Shramko
June 23, 2016
There’s kinda a bug in the code above. The JwtBearerAuthenticationOptions is configured with
AllowedAudiences = new[] { “Any” },
but in fact, the token content does not contain any audience, so that your request gets rejected.
The fastest way to fix that (and not the best one), is to change the way you create a token in the Protect method of the CustomJwtFormat class from
new JwtSecurityToken(_issuer, null, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
to this one
new JwtSecurityToken(_issuer, “Any”, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
i.e. to pass “Any” instead of null as a second constructor parameter.
I'm working on the securing the web API by OAuth bearer token as this working fine in local able to generate token, After hosting on server code is not working to generate token hosted on the server.
when am trying to the server sample Url "api.somedemo.com/token" showing the request responce as method not found ,
but in local "http://local:1234/token" same code working generated token.
I can't able find where am missing on the server to able generate bearer token.
here the code.
From Controller:
private BearerToken GetBearerToken(string userName, string password)
{
BearerToken token = null;
FormUrlEncodedContent bearerTokenContent = OAuthClientWrapper.CreateBearerToken(userName, password);
Uri tokenUri = new Uri("api.somedemo.com/token");
token = OAuthClientWrapper.GetJwtToken(tokenUri, bearerTokenContent);
return token;
}
OAuthClientWrapper Class:
public static FormUrlEncodedContent CreateBearerToken(string userName, string password)
{
var bearerTokenContent = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("username", userName),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("grant_type", "password")
};
return new FormUrlEncodedContent(bearerTokenContent);
}
GetJwtToken method:
public static BearerToken GetJwtToken(Uri uri, FormUrlEncodedContent bearerTokenContent)
{
BearerToken token = null;
try
{
using (var httpClient = new HttpClient())
{
//Set the headers
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
using (HttpResponseMessage response = httpClient.PostAsync(uri, bearerTokenContent).Result)
{
if (response.IsSuccessStatusCode)
{
token = response.Content.ReadAsAsync<BearerToken>().Result;
}
else
{
var reasonPhrase = response.ReasonPhrase;
var result = response.Content.ReadAsStringAsync();
var errorMessage = string.Format("Error: {0} {1} for uri: {2}", reasonPhrase, result, uri.AbsoluteUri);
//Log the error message
token = null;
} //else
} //using
}//using
}//try
catch (AggregateException aex)
{
throw aex;
}//catch
return token;
}
As WebAPI Startup Class:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseJwtBearerAuthentication(new MyJwtOptions());
}
}
Startupoverride :
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
int tokenExpiration = Convert.ToInt32(ConfigurationManager.AppSettings["TokenExpiration"]);
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
//AccessTokenFormat = new JwtFormat(audience, new SymmetricKeyIssuerSecurityTokenProvider(issuer, signingKey)),
AccessTokenFormat = new MyJwtFormat(),
RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(tokenExpiration)
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Application OAuthProviderClass:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
IHttpClientWrapper _HttpClientWrapper = null;
private IEndPoints _ConfigsProviders = null;
public ApplicationOAuthProvider()
{
_HttpClientWrapper = new HttpClientWrapper();
_ConfigsProviders = new EndPoints();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
try
{
var isValidUser = false;
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var url = string.Format("{0}/{1}/{2}", "someAPIBaseUrl", context.UserName, context.Password);
//Getting user information if login is validated in json format.
var returnValue = _HttpClientWrapper.GetStringAsync(url);
var responseObject = JsonConvert.DeserializeObject<ResponseObject>(returnValue);
if (responseObject.Status==true)
{
isValidUser = true;
}
if (!isValidUser)
{
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));
context.Validated(identity);
}
catch (Exception ex)
{
throw ex;
}
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
public MyJwtFormat()
{
_tokenIssuer = ConfigurationManager.AppSettings["TokenIssuer"];
_tokenExpiration = Convert.ToInt32(ConfigurationManager.AppSettings["TokenExpiration"]);
}
ConfigurationManager.AppSettings["TokenIssuer"] given "localhost" in sever also.
Please help to fix this issue
Advance thanks you.
Using postman I can request a token, here it is:
{
"access_token": "N1FL606bmDkZyLplpkLAihaviMQhB042z-rhY262M_W5nSWIv8fDOQiYkEn6GCuDnrxpdOWBS7lpxlBazHYlwnP1RvpDFED1i_ml89QNspyGOWB6TcMkT1MmfUAZ617k9MNvl5UJh2jKzUwvDDeXMURG9tEtmE3UX2L2D-1VA9kqYOzOB1UYbpMAfdTi84jsbR0lhLkNkReQ5fqg4B3IFbbWNGWu5ONb1uuf00ixL-BIMqSvEaNn58_zCyAVFWVzcH2tayYTGT5p_AItKfYiWaYHKC0pDoZ_OBdlpB7Odc7ScwjwFM5vtpBZE81rpk8yjXnrTEk_j9n0eiloJnpWwA",
"token_type": "bearer",
"expires_in": 899,
"refresh_token": "60da311d10f043b892c703c7fb7ab061",
"as:client_id": "Erp",
"userName": "bbauer",
".issued": "Tue, 30 Jun 2015 17:56:10 GMT",
".expires": "Tue, 30 Jun 2015 18:11:10 GMT"
}
I can also get information from an unprotected resource like so:
http://localhost:60689/api/Accounts/User/bbauer
{
"url": "http://localhost:60689/api/accounts/user/31",
"id": 31,
"userName": "bbauer",
"fullName": "Brian Bauer",
"email": null,
"emailConfirmed": false,
"roles": [
"Administrator"
],
"claims": []
}
From that I see that the user is in the "Administrator" role. When I try to get a protected resource, I ALWAYS get this back: "Authorization has been denied for this request."
Here is the method in the controller:
[Authorize(Roles = "Administrator")]
[Route("user/{id:int}", Name = "GetUserById")]
public async Task<IHttpActionResult> GetUser(int id)
{
var user = await AppUserManager.FindByIdAsync(id);
if (user != null)
{
return Ok(TheModelFactory.Create(user));
}
return NotFound();
}
Here are my settings in postman:
http://localhost:60689/api/Accounts/User/31
Content-Type: application/json
Accept: application/json
Authorization: Bearer N1FL606bmDkZyLplpkLAihaviMQhB042z-rhY262M_W5nSWIv8fDOQiYkEn6GCuDnrxpdOWBS7lpxlBazHYlwnP1RvpDFED1i_ml89QNspyGOWB6TcMkT1MmfUAZ617k9MNvl5UJh2jKzUwvDDeXMURG9tEtmE3UX2L2D-1VA9kqYOzOB1UYbpMAfdTi84jsbR0lhLkNkReQ5fqg4B3IFbbWNGWu5ONb1uuf00ixL-BIMqSvEaNn58_zCyAVFWVzcH2tayYTGT5p_AItKfYiWaYHKC0pDoZ_OBdlpB7Odc7ScwjwFM5vtpBZE81rpk8yjXnrTEk_j9n0eiloJnpWwA
I can use fiddler to verify the authorization header is being sent. Another thing to note is when I pass the access_token in to get the unprotected /user/username resource, I can break in code and see the ClaimsPrincipal with these settings:
AuthenticationType: Bearer
IsAuthenticated: true
Name: bbauer
However, if I test User.IsInRole("Administrator") its always false. Why is it false? The AspNetUserRole table has the entry, and when I fetch the user I see his one role of "Administrator"... what on God's green earth am I missing here?
Here is my Startup class if that helps:
public class Startup
{
public static OAuthAuthorizationServerOptions OAuthServerOptions { get; private set; }
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public static string PublicClientId { get; private set; }
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(httpConfig);
app.UseCors(CorsOptions.AllowAll);
app.UseWebApi(httpConfig);
}
public void ConfigureOAuth(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);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
PublicClientId = "self";
OAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(15),
Provider = new SimpleAuthorizationServerProvider(PublicClientId),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}
}
It turns out I needed to add roles to my ClaimsIdentity in my SimpleAuthorizationServerProvider's GrantResourceOwnerCredentials method. Here is the code (see commented section):
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin") ?? "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
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;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim("sub", context.UserName));
//this loop is where the roles are added as claims
foreach (var role in userManager.GetRoles(user.Id))
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", context.ClientId ?? string.Empty
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}