Allow user only access his/her own resource with id in Authorize[] middleare .Net Core Api - asp.net

I am using role based authentication in .Net Core 3.1 Api. I am using Jwt tokens and user claims. Role based authentication works fine. But in some controllers I want to make sure that user gets his/her own data. Because if an employee sends other employee id in a request he/she can get that resource data, I don't want that.
I have email, id and roles in token with some other data.
What I want is that something like [Authorize(Roles="Employee", Id={userId})]
[HttpGet("getUserInventory")]
//[Authorize(Roles="Employee", Claims.Id={userId})]
public IActionResult getUserInventory([FromQuery] int userId)
{
var inventories = _userInventoryExportService.GetGlobalInventory(userId);
if(inventories.Success)
{
return Ok(inventories.Data);
}
return BadRequest(inventories.Message);
}

Have a look at this tutorial we've created at Curity: Securing a .NET Core API. You will see there how to configure authorization based on claims found in a JWT access token.

had the same use case, to authorize user access to its own mailbox only.
controller:
[HttpPost("{address}/inbox/messages/list")]
[Authorize(Policy = "userAddress")]
public async Task<ActionResult<Response>> ListMessages([FromRoute] string address)
{
// return user mailbox data.
}
here i define the userAddress, and also the way i pull the address string from the url. it is not possible to pass this value from the controller, i had to pick it from a global request class:
//Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("userAddress", policy =>
{
policy.RequireAssertion(context =>
{
var userAddress = context.User.FindFirst(JWTClaim.Email).Value;
// /api/v1/mailbox/email#example.com/inbox/messages/list
var address = new HttpContextAccessor().HttpContext.Request.RouteValues["address"].ToString();
return address == userAddress;
});
});
});
it is worth to note that the context contains the actual request values, but is not publicly accessible, only via debugger:
context.Resource.HttpContext.Request.RouteValues["address"].ToString();

Related

How to add/check policy and role .net core API securing with Identityserver4

I have a doubt on API securing with identity server4
IdentityResource
Name
Claims
Roles
role
APIResource
Name
Scopes
testapi
api1
APIScopes
Name
Claims
api1
address
In Startup.cs
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer("Bearer", opt =>
{
opt.Audience = "testapi";
opt.Authority = "https://localhost:5001";
opt.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateAudience = true
};
});
//Policy "Apiscope" created
services.AddAuthorization(opt =>
{
opt.AddPolicy("Apiscope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("Scope", "api1");
});
});
services.AddAuthorization(opt =>
{
opt.AddPolicy("AdminUsers", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("admin");
});
});
In controller
[HttpPost]
[Authorize(Policy = "AdminUsers")]
public IActionResult GetAdminMessage()
{
return Ok("Hello Admin");
}
Is it possible access identity scope in .Net Core API? if yes, How to do?
To get the role value, do I need to add in APIScopes of "api1" userclaims as "address,role" or can do it by above Q1 ?
In Policy "AdminUser", I am checking role by adding "api1" (APIScopes) userclaims as "address,role" but I could not access GetAdminMessage(). How to achieve this?
Is it possible access identity scope in .Net Core API? if yes, How to do?
The claims in the IdentityResources goes into the ID-Token. The content of the ID-Token is "converted" into the ClaimsPrincipal User object when you login in the client (AddOpenIDConnect). After that the ID-token is not used.
To get the role value, do I need to add in APIScopes of "api1" userclaims as "address,role" or can do it by above Q1 ?
APIScopes are on the API Side. API's receives Access-Tokens and it contains the user claism from the ApiSCopes and ApiResources. The claims in the access-token is converted into the user object in the API (using the AddJwtBearer) and you can use the AddPolicy system to authorize the user.
In Policy "AdminUser", I am checking role by adding "api1" (APIScopes) userclaims as "address,role" but I could not access GetAdminMessage(). How to achieve this?
ASP.NET Core and IdentityServer have different opinions on what the claim types (names) should be, so you need to tell AddJwtBearer what the real name is of your role claim, by using:
.AddJwtBearer(opt =>
{
...
opt.TokenValidationParameters.RoleClaimType = "roles";
opt.TokenValidationParameters.NameClaimType = "name";
You can also add this at the top of the ConfigureServicesmethod:
public void ConfigureServices(IServiceCollection services)
{
// By default, Microsoft has some legacy claim mapping that converts
// standard JWT claims into proprietary ones. This removes those mappings.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
If that does not help ,you can also try add:
options.ClaimActions.MapUniqueJsonKey("role", "role");

How to authorize with client credential flow and password flow using opendidict?

I am using opendiddict on net core 3 and to protect my API I was using password flow and doing the authorization process with roles. Now I need to add client credential flow for the same API and I do not know how to do it.Here are my methos in the API:
[Authorize(Roles = "Rol_A,Rol_B", AuthenticationSchemes = "Bearer")]
[HttpGet("message")]
public async Task<IActionResult> GetMessage_A()
{...}
[Authorize(Roles = "Rol_C,Rol_B", AuthenticationSchemes = "Bearer")]
[HttpGet("message")]
public async Task<IActionResult> GetMessage_B()
{...}
But Applications does not have roles. I was reading about using scopes to protect the API but I do not understand how to associate the user and applications with the scopes. On the other hand I tried too, with policies but one method must be decorated with no more than one policy at a time,so if I have multiples combinations of roles it is a mess. I have this anyway:
In startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("Policy_A", policy =>
{
policy.RequireAssertion(context =>
{
//how to evaluate if application has permission
if (context.User.HasClaim(x => x.Type == OpenIdConnectConstants.Claims.Role && (x.Value == "Rol_A" || x.Value == "Rol_B")))
{
return true;
}
return false;
});
});
});
I create my applications with this code:
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "console",
ClientSecret = "388D45FA-B36B-4988-BA59-B187D329C207",
DisplayName = "My client application",
Permissions =
{
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
OpenIddictConstants.Permissions.Prefixes.Scope+"api1"
}
};
await manager.CreateAsync(descriptor);
so my questions are:
Does it make sense what I try to do?
How can I add the permissions that I gave to the descriptor to the token, so it can be use to check in the policy.
To achieve what you want, add all the claims you need in the authentication ticket when handling the token request. To distinguish applications and users, you can add a custom claim indicating whether the token represents an application or a user.
For more information on how to add claims, take a look at the client credentials flow sample: https://github.com/openiddict/openiddict-samples/blob/dev/samples/ClientCredentialsFlow/AuthorizationServer/Controllers/AuthorizationController.cs

Aps .net IdentityServer4 authorize

I'm using IdentityServer4 with asp .net identity as authentication point. My APIs/WebApps call identity server to get access token.
Now, how to authorize uses before some action or inside action in my api/app controller?
I can add roles to access token and then in controller (in web api/web app) use AuthorizeAttribute and check if user IsInRole.
But it means that if I will change user roles, he will see it after logout-login (because roles are part of access token) or token has to expire.
I would like to ask identity server about user role(s) each time I need to authorize him to some action (especially to action like modify/delete some data).
Question how?
Or What I have to looking for?
So there's a few possible solutions here:
Make a call to the OIDC UserInfo Endpoint to obtain updated user claims on every request
Lower the cookie lifetime to refresh user info automatically more often
Implement a custom endpoint on IdentityServer for it to post profile change information to a list of subscribed clients (such as your webapp).
Have IdentityServer force single sign out when user profile data is changed
In terms of difficulty to implement, lowering cookie lifetime is the easiest (just change cookie expiration), but it doesn't guarantee up-to-date claims, and it is visible to the user (frequent redirects to IdentityServer, although no login is required if the access token lifetime is still valid)
Having the webapp call the UserInfo Endpoint on each request is the next easiest (see sample below) but has the worst performance implications. Every request will produce a round trip to IdentityServer.
The endpoint / subscriber model would have the lowest performance overhead. UserInfo requests to IdentityServer would ONLY occur when user profile information has actually changed. This would be a bit more complicated to implement:
On your IdentityServer project, you would need to modify changes to profile data, and post an http message to your webapp. The message could simply contain the user ID of the modified user. This message would need to be authenticated somehow to prevent malicious users from voiding legitimate user sessions. You could include a ClientCredentials bearer token for this.
Your webapp would need to receive and authenticate the message. It would need to store the changed user's ID somewhere accessible to the OnValidatePrincipal delegate (through a service in the DI container most likely)
The Cookie OnValidatePrincipal delegate would then inject this local service to check if user information has changed before validating the principal
Code Samples
Get updated UserInfo from endpoint on each call
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get updated UserInfo from IdentityServer
var accessToken = context.Principal.Claims.FirstOrDefault(c => c.Type == "access_token").Value;
var userInfoClient = new UserInfoClient("https://{IdentityServerUrlGoesHere}");
var userInfoResponse = await userInfoClient.GetAsync(accessToken);
// Invalidate Principal if Error Response
if (userInfoResponse.IsError)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync("NameOfYourCookieAuthSchemeHere");
}
else
{
// Check if claims changed
var claimsChanged = userInfoResponse.Claims.Except(context.Principal.Claims).Any();
if (claimsChanged)
{
// Update claims and replace principal
var newIdentity = context.Principal.Identity as ClaimsIdentity;
newIdentity.AddClaims(userInfoResponse.Claims);
var updatedPrincipal = new ClaimsPrincipal();
context.ReplacePrincipal(updatedPrincipal);
context.ShouldRenew = true;
}
}
}
}
});
Update On Subscribed Change Message from IdentityServer. This example supposes you've created a service (ex IUserChangedService) which stores userIds received at the endpoint from IdentityServer. I don't have samples of the webapp's receiving endpoint or a service.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get User ID
var userId = context.Principal.Claims.FirstOrDefault(c => c.Type == "UserIdClaimTypeHere");
var userChangedService = context.HttpContext.RequestServices.GetRequiredService<IUserChangedService>();
var userChanged = await userChangedService.HasUserChanged(userId);
if (userChanged)
{
// Make call to UserInfoEndpoint and update ClaimsPrincipal here. See example above for details
}
}
}
});
The asp.net core docs have an example of this as well, except working with a local database. The approach of wiring to the OnValidatePrincipal method is the same:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie#reacting-to-back-end-changes
Hope this helps!

Complex authentication with existing user database in MVC5

I'm migrating a SaaS app from Classic ASP to .NET MVC5 and will use EF6 Database First. The login form for end users is customisable by each tenant (on their own subdomain but pointing to the same web application). We wish to use the existing database schema and the new authentication & authorization filters.
For example, a user on one tenant may login by entering their first name, surname and a code generated by our system. A user on another tenant may login by entering their email address and a password. Additionally, each tenant has a separate administrator login which uses a username and password. Another tenant may use LDAP authentication against a remote AD server.
Is there a definitive best practice way of doing custom authentication?
Almost every article appears to suggest different ways of accomplishing this: simply setting FormsAuthentication.SetAuthCookie, using a custom OWIN provider, override AuthorizeAttribute, etc.
In Classic ASP, we queried the database to find out the type of login for that tenant, displayed the appropriate fields on the login screen and then on post back, checked the fields match what's in the database and then set the session variables appropriately which were checked on each page request.
Thanks
I find that Identity framework is very flexible in terms of authentication options. Have a look on this bit of authentication code:
var identity = await this.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
This is pretty standard run of the mill authentication part in Identity, you'll find this in every Identity sample on the web. If you look closely it is very flexible - all you need for authentication is ApplicationUser object that framework does not care how you get.
So in theory you can do things like this (pseudocode, I did not try to compile this):
// get user object from the database with whatever conditions you like
// this can be AuthCode which was pre-set on the user object in the db-table
// or some other property
var user = dbContext.Users.Where(u => u.Username == "BillyJoe" && u.Tenant == "ExpensiveClient" && u.AuthCode == "654")
// check user for null
// check if the password is correct - don't have to do that if you are doing
// super-custom auth.
var isCorrectPassword = await userManager.CheckPasswordAsync(user, "enteredPassword");
if (isCorrectPassword)
{
// password is correct, time to login
// this creates ClaimsIdentity object from the ApplicationUser object
var identity = await this.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// now we can set claims on the identity. Claims are stored in cookie and available without
// querying database
identity.AddClaim(new Claim("MyApp:TenantName", "ExpensiveClient"));
identity.AddClaim(new Claim("MyApp:LoginType", "AuthCode"));
identity.AddClaim(new Claim("MyApp:CanViewProducts", "true"));
// this tells OWIN that it can set auth cookie when it is time to send
// a reply back to the client
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Using this authentication, you have set a few claims on the user - they are stored in the cookie and available everywhere via ClaimsPrincipal.Current.Claims. Claims are essentially a collection of key-value pairs of strings and you can store there anything you like.
I usually access claims from the user via extension method:
public static String GetTenantName(this ClaimsPrincipal principal)
{
var tenantClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:TenantName");
if (tenantClaim != null)
{
return tenantClaim.Value;
}
throw new ApplicationException("Tenant name is not set. Can not proceed");
}
public static String CanViewProducts(this ClaimsPrincipal principal)
{
var productClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:CanViewProducts");
if (productClaim == null)
{
return false;
}
return productClaim.Value == "true";
}
So in your controller/view/business layer you can always call to ClaimsPrincipal.Current.GetTenantName() and in this case you'd get "ExpensiveClient" back.
Or if you need to check if a specific feature is enabled for the user, you do
if(ClaimsPrincipal.Current.CanViewProducts())
{
// display products
}
It is up to you how you store your user properties, but as long as you set them as claims on the cookie, they will be available.
Alternatively you can add claims into the database for every user:
await userManager.AddClaimAsync(user.Id, new Claim("MyApp:TenantName", "ExpensiveClient"));
And this will persist the claim into the database. And by default, Identity framework adds this claim to the user when they login without you needing to add it manually.
But beware, you can't set too many claims on a cookie. Cookies have 4K limit set by browsers. And the way Identity cookie encryption works it increases encoded text by about 1.1, so you can have roughly 3.6K of text representing claims. I've run into this issue here
Update
To control access to controllers via claims you can use the following filter on the controller:
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
public string Name { get; private set; }
public ClaimsAuthorizeAttribute(string name)
{
Name = name;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = HttpContext.Current.User as ClaimsPrincipal;
if (user.HasClaim(Name, Name))
{
base.OnAuthorization(filterContext);
}
else
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary()
{
{"controller", "errors"},
{"action", "Unauthorised"}
});
}
}
}
and then use this attribute on controllers or separate actions like this:
[ClaimsAuthorize("Creating Something")]
public ActionResult CreateSomething()
{
return View();
}
User will require "Create Something" claim on them to access this action, otherwise they will be redirected to "Unauthenticated" page.
Recently I've played with claims authentication and made a prototype application similar to your requirement. Please have a look on the simple version: https://github.com/trailmax/ClaimsAuthorisation/tree/SimpleClaims where claims are stored individually for each user. Or there is more complex solution where claims belong to a role and when users login, role claims assigned to the user: https://github.com/trailmax/ClaimsAuthorisation/tree/master
There's two components you need. The authentication itself and the strategy each user gets for authentication.
The first is easy and is accomplished with these two lines...
var identity = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{ IsPersistent = isPersistent }, identity);
When a user is Signed In, they get an identity which contains the user's claims on roles and who they are. These are given to the user as a cookie. After this point you just decorate controllers with [Authorize] to make sure only authenticated users can log in. Pretty standard here.
The only complicated part in the problem is the second part; The strategy for how each user gets authenticated set by the admin.
Some pseudocode for how this could work in actions is this...
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(int tenantId)
{
var tenant = DB.GetTenant(tenantId);
return View(tenant);
}
In your view you would output the authentication strategy for the tenant. That may be email and password, a code and email, or whatever your requirements.
When the user enters their info and clicks to login, you then have to determine what strategy they were using, and check to see if their information matches.
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model)
{
var tenant = DB.GetTenant(model.tenantId);
//If user info matches what is expected for the tenants strategy
if(AuthenticateUserInfo(tenant, model.UserInputs))
{
//Sign the user in
var identity = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{ IsPersistent = isPersistent }, identity);
}
}
I did a lot of hand-waving in the second part because of the complicated nature of how dynamic it is. Overall you should use the same strategies you used in your legacy application to generate the right inputs and such. Nothing has changed there, only the way you sign in is going to be different.
Using Visual Studio 2013 Update 3 you can create a new Web Application that comes with MVC5, EF6 and Identity already installed. Here is how to select Identity when you create a new Application:
With MVC Template selected, click Change Authentication and the highlighted window will pop up. Individual User Accounts = Identity. Click ok and continue.
Having done that, you have created an application with Identity. You can now customize your login and registration as follows.
You want to look at your AccountController.cs in the Controllers folder. Here you will find the script for Registration and Login.
If you look at the
public async Task<ActionResult> Register(RegisterViewModel model)
function, you'll notice it contains:
IdentityResult result = await UserManager.CreateAsync(new ApplicationUser() { UserName = newUser.UserName }, newUser.Password);
This is where the user gets created. If you want to use Identity, you should save the users username and password. You can use an e-mail as the username if you want. etc.
After doing that, I add the user a specified role (I find the user and then add it to the role):
ApplicationUser userIDN = UserManager.FindByName(newUser.UserName);
result = await UserManager.AddToRoleAsync(userIDN.Id, "Admin");
In my scenario, I have created an additional extended table where I hold their address, phone number, etc. In that table, you can hold any additional login information. You can add these new entries before or after creating the users account in Identity. I would create the extended information and then create the Identity account just to be sure.
IMPORTANT: For any scenarios where a user is logging in with something that is not a username or e-mail address that isn't saved into via Identity, you will have to do a custom solution.
Example: User types in their first name, surname and the code. You could do two things: Save the first name and surname into the username field of identity and the code into the password and verify the login that way
OR
you would check your custom table for those properties and make sure they match, if and when they do you could call this little beauty:
await SignInAsync(new ApplicationUser() { UserName = model.UserName }, isPersistent: false);
Once you call that SignInAsync function, you can go ahead and direct them to your protected page.
NOTE: I'm creating the ApplicationUser on the function call but if you use it more than once it would be ideal for you to declare the ApplicationUser as follows:
ApplicationUser user = new ApplicationUser() { UserName = model.UserName };
NOTE #2: If you don't want to user Async methods, those functions all have non-async versions of them.
Note #3: At the very top of any page using UserManagement, it is being declared. Make sure if you are creating your own controller that wasn't generated by Visual Studio to use Identity, you include the UserManagement declaration script at the top inside of the class:
namespace NameOfProject.Controllers
{
[Authorize]
public class AccountController : Controller
{
public AccountController() : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()))) { }
public AccountController(UserManager<ApplicationUser> userManager) { UserManager = userManager; }
public UserManager<ApplicationUser> UserManager { get; private set; }
Please let me know if you have any questions and I hope this helps.

Passing the username/password from client to web API using GET

for example I have a web API : http://example.com/api/product.
I have a C# client to consume this web API. Something like that to get whole list of product.
// List all products.
HttpResponseMessage response = client.GetAsync("api/products").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result;
foreach (var p in products)
{
Console.WriteLine("{0}\t{1};\t{2}", p.Name, p.Price, p.Category);
}
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
How do I pass the username and password from C# client to server's API? What I want is when the C# client to get whole product list from web API.
The client will send the username and password to the server's API. if the server's web API checks whether it is authorized user from database, if not don't let it get product list.
I used the following approach in a proof of concept some time ago, I hope it helps you.
I wrote something like this, an "AuthenticationController" with 2 methods:
public bool Login(string username, string password, bool rememberMe)
{
if (Membership.ValidateUser(username, password))
{
FormsAuthentication.SetAuthCookie(username, rememberMe);
return true;
}
return false;
}
public void Logout()
{
FormsAuthentication.SignOut();
}
The Login method creates a cookie that will be sent to the client; then, in each request, you need to send it back to the server. You can use the [Authorize] attribute in your controller actions to validate allowed roles and rights.
My recommendation is to use have an authentication routine that will assign a token to the client. The client would then cache that token and pass that token in subsequent requests. The authentication routine should be via SSL to prevent sniffing on the wire and shouldn't be stored on the device at all (the token can be cached to the device).
This will give you a fair bit of control over the client. Your service is then in a position where it can preemptively deactivate the client (kill the token and force a re-auth - essentially a timemout situation). You are also in a position to protect your application on the client (if the application is compromised on the device the user credentials won't be passed around).
You could use DotNetOpenAuth to get you started along this path.
[System.Web.Mvc.AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(string loginIdentifier)
{
if (!Identifier.IsValid(loginIdentifier))
{
ModelState.AddModelError("loginIdentifier",
"The specified login identifier is invalid");
return View();
}
else
{
var openid = new OpenIdRelyingParty();
IAuthenticationRequest request = openid.CreateRequest(
Identifier.Parse(loginIdentifier));
// Require some additional data
request.AddExtension(new ClaimsRequest
{
BirthDate = DemandLevel.NoRequest,
Email = DemandLevel.Require,
FullName = DemandLevel.Require
});
return request.RedirectingResponse.AsActionResult();
}
}
Source: Sample Code

Resources