OAuth bearer token validation by Authorize Attribute - asp.net

I'm stuck with OAuth token authorization. I have configured OAuth and I have my own OAuth server provider.
Configuration code:
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AuthorizeEndpointPath = new PathString("/authorize"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
Provider = new SimpleAuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
Server provider:
public class SimpleAuthorizationServerProvider : 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[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(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);
}
}
When I'm sending: grand_type=password, username=MyUserName, password=MyPassword to the OAuth token endpoint "localhost/token", it's nicely creating my OAuth bearer token. But from here, I have no idea how to use this generated token, where it is stored (how to get it), and how to make a success validation using [Authorize] attribute on ASP.NET MVC controller. I just simply want to use my generated token, when I'm going from one view to another, that has [Authorize] attribute, and pass through it successfully. How can I achive this ?

Add new overriden function in "SimpleAuthorizationServerProvider" class.
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);
}
Then you can get token in json object.

Implement the following workflow :
Retrieve an access token via password grant-type
Get user information by passing this access token. Link : http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
Store the claims into the cookie

Related

How do I make and use JWT role based authentication?

I follow the tutorial link below.
https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login
I am trying to understand how it works and I want to use role-based authentication using this token. so I made another policy in the Startup.cs file as below.
And I tried to use it like [Authorize(Policy = "admin")] or [Authorize(Policy = "ApiUser")]in the controller but every time I try I get unauthenticated using postman.
What am I missing? how to make roles-based authentication based on the tutorial?
Startup
services.AddAuthorization(options =>
{
options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
services.AddAuthorization(options =>
options.AddPolicy("admin", policy => policy.RequireRole("admin"))
);
Auth Controller
// POST api/auth/login
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]CredentialsViewModel credentials)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
if (identity == null)
{
//return null;
return BadRequest(Error.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState));
}
var id = identity.Claims.Single(c => c.Type == "id").Value;
var user = await _userManager.FindByIdAsync(id);
IList<string> role = await _userManager.GetRolesAsync(user);
var jwt = await Tokens.GenerateJwt(identity, role[0], _jwtFactory, credentials.UserName, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented });
return new OkObjectResult(jwt);
}
I tried with all method and none of them working
[Authorize(Policy = "ApiUser")]
[HttpGet("getPolicy")]
public string GetPolicy()
{
return "policyWorking";
}
[Authorize(Roles = "admin")]
[HttpGet("getAdmin")]
public string GetAdmin()
{
return "adminWorking";
}
[Authorize ]
[HttpGet("getAuthorize")]
public string GetAuthorize()
{
return "normal authorize Working";
}
Firstly,be sure you have add json string in your appsettings.json otherwise you would always get 401 unauthorized:
"JwtIssuerOptions": {
"Issuer": "webApi",
"Audience": "http://localhost:5000/"
}
What am I missing? how to make roles-based authentication based on the tutorial?
1.If you want to use the following way to register the service:
services.AddAuthorization(options =>
options.AddPolicy("admin", policy => policy.RequireRole("admin"))
);
The authorize attribute should be like below:
[Authorize(Policy = "admin")]
2.If you want to use the following way:
[Authorize(Roles = "admin")]
You need to remove the service from Startup.cs:
//services.AddAuthorization(options =>
// options.AddPolicy("admin", policy => policy.RequireRole("admin"))
//);
Then,don't forget to add claim with role in JwtFactory.GenerateEncodedToken like below:
public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Rol),
identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Id),
new Claim(ClaimTypes.Role,"admin")
};
//...
}
I've made mine using this tutorial.
It also works with .Net Core 3.1.
#Update
I'm using policies for that:
services.AddAuthorization(options => {
options.AddPolicy(Policies.RequireAny, policy =>
policy.RequireClaim(JwtClaimTypes.Role, Enum.GetNames(typeof(AvailableRoles))));
options.AddPolicy(Policies.RequireAdmin, policy =>
policy.RequireClaim(JwtClaimTypes.Role,
AvailableRoles.Admin.ToString()));
});
And in Controller
[HttpDelete("{id:int:min(1)}")]
[Authorize(Policy = Policies.RequireAdmin)]
public async Task<IActionResult> Delete(int id) {
// CODE
return Ok();
}
and in creating JWT Token:
// Other claims and code...
claims.Add(new Claim(JwtClaimTypes.Role, roles[0]))
var token = new JwtSecurityToken(
issuer:_appSettings.AppUrl,
audience:_appSettings.AppUrl,
claims: claims,
expires: expires,
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);

ASP.NET Core 2.0 Bearer Auth without Identity

I thought I had a pretty simple goal in mind when I set out a day ago to implement a self-contained bearer auth webapi on .NET core 2.0, but I have yet to get anything remotely working. Here's a list of what I'm trying to do:
Implement a bearer token protected webapi
Issue tokens & refresh tokens from an endpoint in the same project
Use the [Authorize] attribute to control access to api surface
Not use ASP.Net Identity (I have much lighter weight user/membership reqs)
I'm totally fine with building identity/claims/principal in login and adding that to request context, but I've not seen a single example on how to issue and consume auth/refresh tokens in a Core 2.0 webapi without Identity. I've seen the 1.x MSDN example of cookies without Identity, but that didn't get me far enough in understanding to meet the requirements above.
I feel like this might be a common scenario and it shouldn't be this hard (maybe it's not, maybe just lack of documentation/examples?). As far as I can tell, IdentityServer4 is not compatible with Core 2.0 Auth, opendiddict seems to require Identity. I also don't want to host the token endpoint in a separate process, but within the same webapi instance.
Can anyone point me to a concrete example, or at least give some guidance as to what best steps/options are?
Did an edit to make it compatible with ASP.NET Core 2.0.
Firstly, some Nuget packages:
Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.AspNetCore.Identity
System.IdentityModel.Tokens.Jwt
System.Security.Cryptography.Csp
Then some basic data transfer objects.
// Presumably you will have an equivalent user account class with a user name.
public class User
{
public string UserName { get; set; }
}
public class JsonWebToken
{
public string access_token { get; set; }
public string token_type { get; set; } = "bearer";
public int expires_in { get; set; }
public string refresh_token { get; set; }
}
Getting into the proper functionality, you'll need a login/token web method to actually send the authorization token to the user.
[Route("api/token")]
public class TokenController : Controller
{
private ITokenProvider _tokenProvider;
public TokenController(ITokenProvider tokenProvider) // We'll create this later, don't worry.
{
_tokenProvider = tokenProvider;
}
public JsonWebToken Get([FromQuery] string grant_type, [FromQuery] string username, [FromQuery] string password, [FromQuery] string refresh_token)
{
// Authenticate depending on the grant type.
User user = grant_type == "refresh_token" ? GetUserByToken(refresh_token) : GetUserByCredentials(username, password);
if (user == null)
throw new UnauthorizedAccessException("No!");
int ageInMinutes = 20; // However long you want...
DateTime expiry = DateTime.UtcNow.AddMinutes(ageInMinutes);
var token = new JsonWebToken {
access_token = _tokenProvider.CreateToken(user, expiry),
expires_in = ageInMinutes * 60
};
if (grant_type != "refresh_token")
token.refresh_token = GenerateRefreshToken(user);
return token;
}
private User GetUserByToken(string refreshToken)
{
// TODO: Check token against your database.
if (refreshToken == "test")
return new User { UserName = "test" };
return null;
}
private User GetUserByCredentials(string username, string password)
{
// TODO: Check username/password against your database.
if (username == password)
return new User { UserName = username };
return null;
}
private string GenerateRefreshToken(User user)
{
// TODO: Create and persist a refresh token.
return "test";
}
}
You probably noticed the token creation is still just "magic" passed through by some imaginary ITokenProvider. Define the token provider interface.
public interface ITokenProvider
{
string CreateToken(User user, DateTime expiry);
// TokenValidationParameters is from Microsoft.IdentityModel.Tokens
TokenValidationParameters GetValidationParameters();
}
I implemented the token creation with an RSA security key on a JWT. So...
public class RsaJwtTokenProvider : ITokenProvider
{
private RsaSecurityKey _key;
private string _algorithm;
private string _issuer;
private string _audience;
public RsaJwtTokenProvider(string issuer, string audience, string keyName)
{
var parameters = new CspParameters { KeyContainerName = keyName };
var provider = new RSACryptoServiceProvider(2048, parameters);
_key = new RsaSecurityKey(provider);
_algorithm = SecurityAlgorithms.RsaSha256Signature;
_issuer = issuer;
_audience = audience;
}
public string CreateToken(User user, DateTime expiry)
{
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.UserName, "jwt"));
// TODO: Add whatever claims the user may have...
SecurityToken token = tokenHandler.CreateJwtSecurityToken(new SecurityTokenDescriptor
{
Audience = _audience,
Issuer = _issuer,
SigningCredentials = new SigningCredentials(_key, _algorithm),
Expires = expiry.ToUniversalTime(),
Subject = identity
});
return tokenHandler.WriteToken(token);
}
public TokenValidationParameters GetValidationParameters()
{
return new TokenValidationParameters
{
IssuerSigningKey = _key,
ValidAudience = _audience,
ValidIssuer = _issuer,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(0) // Identity and resource servers are the same.
};
}
}
So you're now generating tokens. Time to actually validate them and wire it up. Go to your Startup.cs.
In ConfigureServices()
var tokenProvider = new RsaJwtTokenProvider("issuer", "audience", "mykeyname");
services.AddSingleton<ITokenProvider>(tokenProvider);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = tokenProvider.GetValidationParameters();
});
// This is for the [Authorize] attributes.
services.AddAuthorization(auth => {
auth.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
Then Configure()
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
// Whatever else you're putting in here...
app.UseMvc();
}
That should be about all you need. Hopefully I haven't missed anything.
The happy result is...
[Authorize] // Yay!
[Route("api/values")]
public class ValuesController : Controller
{
// ...
}
Following on #Mitch answer: Auth stack changed quite a bit moving to .NET Core 2.0. Answer below is just using the new implementation.
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace JwtWithoutIdentity
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = "me",
ValidAudience = "you",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret
};
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
}
}
Token Controller
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using JwtWithoutIdentity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace JwtWithoutIdentity.Controllers
{
public class TokenController : Controller
{
[AllowAnonymous]
[Route("api/token")]
[HttpPost]
public async Task<IActionResult> Token(LoginViewModel model)
{
if (!ModelState.IsValid) return BadRequest("Token failed to generate");
var user = (model.Password == "password" && model.Username == "username");
if (!user) return Unauthorized();
//Add Claims
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.UniqueName, "data"),
new Claim(JwtRegisteredClaimNames.Sub, "data"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("me",
"you",
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new JsonWebToken()
{
access_token = new JwtSecurityTokenHandler().WriteToken(token),
expires_in = 600000,
token_type = "bearer"
});
}
}
}
Values Controller
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JwtWithoutIdentity.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[Authorize]
[HttpGet]
public IEnumerable<string> Get()
{
var name = User.Identity.Name;
var claims = User.Claims;
return new string[] { "value1", "value2" };
}
}
}
Hope this helps!

how to use SignInAsync in webapi

I am trying to login user through webapi.
My apicontroller function is:
public async Task<IHttpActionResult> Login(string email, string password)
{
ApplicationDbContext ctx = new ApplicationDbContext();
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(ctx);
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(store);
var user = await UserManager.FindAsync(email, password);
if (user != null)
{
await SignInAsync(user, true); // The name 'SignInAsync' does not exist in current context
return Ok("OK");
}
return Ok("Error");
}
I want to write methods of signup, login, and logout in webapi but i am stuck at SignInAsync. Am I missing library reference? Or how to use this in webapi?
SignInAsync is a method of SignInManager class not controller class write this instead:
await HttpContext.Current.GetOwinContext()
.Get<ApplicationSignInManager>().SignInAsync(user, true, false);

Identity Authorize Attribute Roles with Web API

I have a small Web API application that uses Identity to manage users using Owin Bearer Tokens. The basics of this implementation work fine: I can register a user, login a user and access Web API end points that are marked with [Authorize].
My next step is to limit Web API endpoints using roles. For example, a controller that only users in the Admin role can access. I've created the Admin user as below and I add them to the Admin role. However when I update my existing controllers from [Authorize] to [Authorize(Roles = "Admin")] and try to access it using the Adim account, I get a 401 Unauthorized.
//Seed on Startup
public static void Seed()
{
var user = await userManager.FindAsync("Admin", "123456");
if (user == null)
{
IdentityUser user = new IdentityUser { UserName = "Admin" };
var createResult = await userManager.CreateAsync(user, "123456");
if (!roleManager.RoleExists("Admin"))
var createRoleResult = roleManager.Create(new IdentityRole("Admin"));
user = await userManager.FindAsync("Admin", "123456");
var addRoleResult = await userManager.AddToRoleAsync(user.Id, "Admin");
}
}
//Works
[Authorize]
public class TestController : ApiController
{
// GET api/<controller>
public bool Get()
{
return true;
}
}
//Doesn't work
[Authorize(Roles = "Admin")]
public class TestController : ApiController
{
// GET api/<controller>
public bool Get()
{
return true;
}
}
Q: What is the correct way to set up and use roles?
How do you set the claims for the users when they login I believe you are missing this line of code in method GrantResourceOwnerCredentials
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Supervisor"));
And if you want to create the identity from DB use the below:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
return userIdentity;
}
Then in GrantResourceOwnerCredentials do the below:
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType);

ASP.NET WebAPI2 BearerToken redirects to Login page instead of returning 401

I created a new web application containing MVC5 and WebAPI. Now I added a new API-Controller and added an Authorize-Atribute (System.Web.Http). Authorizatin works fine, and values are returned. But when the authorization fails, the login-page and status 200 are returned. But i need the error and status 401 to be returned in that case.
I tried and when I remove app.UseCookieAuthentication, the correct status is returned - but I need cookie auth to enable authentication in the normal MVC part.
Of course, I could override Authorization attribute to suppress forms redirect as advised in this solution but I guess this is not the correct way as there exist 2 authorizeattributes and a lot of configuration stuff.
my startup auth:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager,
ApplicationUser>(TimeSpan.FromMinutes(30),
(manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens( new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/Account/Token"),
Provider = new SimpleAuthorizationServerProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true,
});
}
my oauth provider:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == "self")
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
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);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var info = await userManager.FindAsync(context.UserName, context.Password);
if (info == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var oAuthIdentity = await userManager.CreateIdentityAsync(info, context.Options.AuthenticationType);
var cookiesIdentity = await userManager.CreateIdentityAsync(info, CookieAuthenticationDefaults.AuthenticationType);
var properties = new AuthenticationProperties(new Dictionary<string, string> {
{ "userName", info.UserName }
});
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}
You can override the 302 Response:
var c = new CookieAuthenticationOptions;
// Make ASP.NET give us the 302 redirect when cookie is missing/broke
c.AutomaticChallenge = true;
c.Events = new CookieAuthenticationEvents
{
// Override the 302 redirection with the 401 we actually want
OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.FromResult(0); ;
}
};
This is an unpleasant hack, it works, but I'd love to know a better way of making the built in authentication middleware work as expected.

Resources