JWT auth with [Authorize] attribute - asp.net-web-api

Could someone shed a light on how default Asp.Net core attribute [Authorize] (with using Asp.Net Identity) understands how it should decode JWT token and get necessary info for granted access?
While forming JWT token I put in token RoleClaims for the user, does [Authorize] base on claims to grant them access to specific actionMethod

You can access your claims using the following code:
User.Claims.FirstOrDefault(el => el.Type == claim)?.Value

More details please follow this link
Create JWT & Authorize
[Authorize]
[HttpPost]
public string Post()
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
IEnumerable<Claim> claim = identity.Claims;
var UserName = claim.Where(c => c.Type == "UserName").Select(c => c.Value).SingleOrDefault();
return "Welcome to " + UserName + "!";
}

Related

How to best implement simple validation of JWT credential to data being passed to my controller

My ASPNetCore web API is using JWT (Json Web Tokens) for authentication. The JWT token has an external and internal user ID inside it. Having these ID's in the JWT does not concern me, as JWT's can't be tampered with or they become invalid, and the internal ID is not useful anywhere outside the system. Of course, the password is not in the JWT content.
Within the JWT, the external user ID becomes the user's System.Security.Claims.ClaimType.Name. The internal ID is set as a JwtRegisteredClaimName.UniqueName value.
When calls are made to the web API, it is good that the [Authorize] attribute attribute makes sure that the user has authenticated and has a currently valid JWT. The concern I have is that once the user is logged in, there is an opportunity for hacking by using the Web API, sending external or internal user id's as criteria that do not match the currently authenticated user. Some web methods in the controllers accept the internal user ID as part of the request being posted, for example, a call to save user information has the internal user ID inside, used as the key for saving the data. I need to be sure that the authenticated user matches/is the same as the user whose data is being saved via the Web API.
My Question is how and where to best implement this data-level security in my web api? Policies don't seem like they can be applied against the data being passed. Authorization filters don't seem to have access to the message body nor any data bindings. Action filters (Microsoft.ASPNetCore.MVC.Filters) run later, but seem like they may not really be intended for this. Also, how do you access the body of the message that was posted inside an action filter? Or should I always make sure that the user ID is passed to methods as a consistently named parameter that I can access via ActionExecutingContext.ActionArguments?
I've searched many posts and not found any scenarios that match what I'm trying to do.
You can always use Middleware to intercept the call when the Response object has been populated , see code sample form here and here .
Authorization filters could also read the request body with EnableRewind :
public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var request = context.HttpContext.Request;
context.HttpContext.Request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
var requestBody = stream.ReadToEnd();
}
}
}
Also works in action filters :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ReadableBodyStreamAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var request = actionContext.HttpContext.Request;
var route = request.Path.HasValue ? request.Path.Value : "";
var requestHeader = request.Headers.Aggregate("", (current, header) => current + $"{header.Key}: {header.Value}{Environment.NewLine}");
var requestBody = "";
request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
requestBody = stream.ReadToEnd();
}
if (...)
{
var wrongResult = new { error = "Wrong parameters" };
actionContext.Result = new JsonResult(wrongResult);
}
}
}

ASP.NET Core custom defined User Roles implementation

I am trying to define a custom way of User Roles since my DB's structure for the User table is the following structure:
Role is a bool so if it's true the User is an Admin, else he's a normal User.
I know I need to declare the add.UseAuthorization() in Startup.cs. and I can add the Attribute [Roles="Administrator"] / [Roles="User"] inside the Controller but I am not sure how to define the role to be determined by my Role column from the User table.
I've been searching the internet, reading about Policies too but I don't think that's the right way to implement. Everything I've found online is about some sort Identity structure but doesn't make any sense on how to attach it to my Role column.
Hope, someone can help me out. Thanks!
If you are free to manipulate your DB, I would highly suggest using IdentityFramework, it's a powerful framework which can integrate in your own database.
But to answer your question specifically, there's two steps missing:
Pick an authentication scheme to login the user (e.g. Cookie-based, ...)
Once the user logs in, save the designed Role in a ClaimsPrincipal object. This way the [Authorize(Roles = "User")] declaration can pick this up.
Below you'll find a basic example using the default ASP.NET Core template in Visual Studio.
Add the Authentication middleware your ConfigureServices method, and configure it using a AuthenticationScheme. In this case I'm using Cookie authentication.
//in ConfigureServices, add both middlewares
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
//in the Configure() method, enable these middlewares
app.UseAuthentication();
app.UseCookiePolicy(new CookiePolicyOptions());
Now you're ready for action. Let's say you have an Action method in which you want to authenticate the user. This is where you want to transform your Role so it can be recognised by [Authorize]
Get the value you need from your database. You'd end up with a bool. Convert it to a Role Claim, and add that to a ClaimsIdentity.
bool roleFromDb = true; //this comes from db
//convert to Claim of "Role" type, and create a ClaimsIdentity with it
var adminClaim = new Claim(ClaimTypes.Role, roleFromDb ? "Administrator" : "User");
var claimIdentity = new ClaimsIdentity(new[] { adminClaim },
CookieAuthenticationDefaults.AuthenticationScheme);
//signs in the user and add the ClaimsIdentity which states that user is Admin
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimIdentity));
With that done, you can mark other actionmethods using the [Authorize] attribute, e.g:
[Authorize(Roles = "User")]
public IActionResult About() { ... }
[Authorize(Roles = "Administrator")]
public IActionResult Contact() { ... }
Now, only a signed in user with the "Administrator" role can visit the Contact page.
Check this resource for a more finetuned configuration of the middleware used.
Another way of implementing, based on my database without any modifications, is to use Claims and Cookies. I've managed doing this reading the following documents
Link One
Link Two
I've encountered only one major issue which was solved by reading this.
I'll also add the Login method and the Startup.cs rows, so others can see how to use it (if the docs aren't enough).
Login method from the Controller
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> Login(UserModel userModel)
{
if (_iUserBus.LoginUser(userModel))
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, userModel.Email),
new Claim(ClaimTypes.Role, _iUserBus.GetRole(userModel.Email)),
};
ClaimsIdentity userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
var authProperties = new AuthenticationProperties
{
IsPersistent = false,
};
await HttpContext.SignInAsync(principal, authProperties);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("Password", "Email and/or Password wrong");
return View();
}
}
Startup.cs
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.LoginPath = "/Users/Login";
options.LogoutPath = "/Users/Logout";
});
Hope this is useful to anyone in need.

How to do Role-based Web API Authorization using Identity Server 4 (JWT)

This is all new to me and I'm still trying to wrap my head around it. I've got an IDP (Identity Server 4) set up, and I was able to configure a client to authenticate to it (Angular 6 App), and further more to authenticate to an API (Asp.Net Core 2.0). It all seems to work fine.
Here's the client definition in the IDP:
new Client
{
ClientId = "ZooClient",
ClientName = "Zoo Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = true,
RedirectUris = { "http://localhost:4200/home" },
PostLogoutRedirectUris = { "http://localhost:4200/home" },
AllowedCorsOrigins = { "http://localhost:4200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Address,
"roles",
"ZooWebAPI"
}
}
I'm requesting the following scopes in the client:
'openid profile email roles ZooWebAPI'
The WebAPI is set up as such:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonFormatters()
.AddAuthorization();
services.AddCors();
services.AddDistributedMemoryCache();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:44317";
options.RequireHttpsMetadata = false;
options.ApiName = "ZooWebAPI";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(policy =>
{
policy.WithOrigins("http://localhost:4200");
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.AllowCredentials();
policy.WithExposedHeaders("WWW-Authenticate");
});
app.UseAuthentication();
app.UseMvc();
}
By using [Authorize] I was successfully able to secure the API:
[Route("api/[controller]")]
[Authorize]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public ActionResult Get()
{
return new JsonResult(User.Claims.Select(
c => new { c.Type, c.Value }));
}
}
Everything works fine, if client is not authenticated, browser goes to IDP, requires authentication, redirects back with access token, access token is then used for API calls that are successfully made.
If I look at the Claims in the User object, I can see some information, but I don't have any user information. I can see the scopes, and etc, but no roles for example. From what I read, that is to be expected, and the API should not care about what user is calling it, but how would I go by restricting API calls based on roles? Or would that be completely against specs?
The IDP has an userinfo end point that returns all the user information, and I thought that would be used in the WebAPI, but again, from some reading, it looks like the intention is for that end point to be called from the client only.
Anyway, I would like to restrict Web API calls based on the roles for a specific user. Does anyone have any suggestions, comments? Also, I would like to know what user is making the call, how would I go by doing that?
JWT example:
Thanks
From what I can learn from your information, I can tell the following.
You are logging in through an external provider: Windows Authentication.
You are defining some scopes to pass something to the token that indicates access to specific resources.
The User object you speak of, is the User class that gets filled in from the access token. Since the access token by default doesn't include user profile claims, you don't have them on the User object. This is different from using Windows Authentication directly where the username is provided on the User Principle.
You need to take additional action to provide authorization based on the user logging in.
There a couple of points where you can add authorization logic:
You could define claims on the custom scopes you define in the configuration of Identityserver. This is not desirable IMHO because it's fixed to the login method and not the user logging in.
You could use ClaimsTransformation ( see links below). This allows you to add claims to the list of claims availible at the start of your methods. This has the drawback ( for some people an positive) that those extra claims are not added to the access token itself, it's only on your back-end where the token is evaluated that these claims will be added before the request is handled by your code.
How you retrieve those claims is up to your bussiness requirements.
If you need to have the user information, you have to call the userinfo endpoint of Identityserver to know the username at least. That is what that endpoint is intended for. Based on that you can then use your own logic to determine the 'Roles' this user has.
For instance we created an separate service that can configure and return 'Roles' claims based upon the user and the scopes included in the accesstoken.
UseClaimsTransformation .NET Core
UseClaimsTransformation .NET Full framework

WebAPI 2 secure routing parameter

I have a situation where I don't want to pass logged-in userid in API route parameter.
For example there is a WebAPI [GET or POST] endpoint 'api/getUserDetails/{id}'. This id is passed to another layer to get the user data.
public RestResult getUserDetails(int uID)
{
int uID = getFromHeaderCookie();
var userDetails = someService.GetUserDetail(uID);
return userDetails;
}
Here id is passed as route parameter and by changing the Id value anyone can see the other users data.
So now I want that the url should be like 'api/getUserDetails' and I am reading Id from header Cookie saved at the time of login. using ActionFilters I can manage to get the Id.
public RestResult getUserDetails( )
{
int uID = getFromHeaderCookie();
var userDetails = someService.GetUserDetail(uID);
return userDetails;
}
This is working but not looking good as there are other methods where I want to secure UserId.
Is there any way to remove int uID = getFromHeaderCookie(); and use some custom model binder to inject userID in controller?
Thanks in advance
You can try Oauth, and get user info from ClaimsIdentity, just need to add Authorization(such as Bearer AccessToken) when request url.

How to tell if ASP.NET impersonation is working?

With ASP.NET impersonation, can one use Environment.UserName to determine if impersonation is working? That is if the site is impersonating properly, should Environment.UserName return my username?
You should use User.Identity.Name:
[Authorize]
public ActionResult Foo()
{
// If we got so far it means that the user is authorized to
// execute this action according to our configuration =>
// we can work with his username
string username = User.Identity.Name;
...
}

Resources