Blazor WASM - Identity RoleClaims - identityserver4

In my previous MVC projects, I was using default identity pages to login (/Identity/Account/Login)
By using the code below on my Controller.cs, I can get various values like the roles that current user is assigned to, the claims of the roles, etc...
var claims = User.Claims; // or HttpContext.User.Claims
//expected claim values are:
//{the-user-guid}
//email#test.com
//["Admin", "Manager"]
//Permission.Module1.Create <-- I need this (in Blazor)
//Permission.Module2.Read <-- I need this (in Blazor)
//and so on...
However, having the same setup with Blazor, calling User.Claims doesn't include the roles and the claims of the roles by default.
I was able to include the roles (ie. Admin, Manager) that user is assigned to by doing this. So the next bit I am aiming to achieve is getting the RoleClaims (from AspNetRoleClaims table) - which would give me the Permissions.
In the context of the solution from the link, I am not sure if there are other "keywords" I can use (apart from "role") to be able to get the RoleClaims. I would also appreciate it if you could point me to a resource with the list of these keywords.

You will need to inject The following provider in your page
#inject Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider AuthenticationStateProvider
Then use it as following
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
user = authState.User;
var claims = user.Claims?.ToList();
Also if you want to secure your pages based on Roles/policy
you could add the policy in program.cs
as following
builder.Services.AddAuthorizationCore(options =>
{
options.AddPolicy("Admin", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin");
});
});
Then use it within your partial class as following
[Authorize(Policy = "Admin")]
or within your page as following
#using Microsoft.AspNetCore.Authorization
#attribute [Authorize(Policy ="Admin")]
Good luck
Edit April 28, 2021
Thanks for the Update Majo
If you want to get the user information within the controller
you will need to inject the UserManager in your controller constructor
private readonly ILogger<WeatherForecastController> _logger;
private readonly UserManager<ApplicationUser> userManager;
public WeatherForecastController(ILogger<WeatherForecastController> logger ,
UserManager<ApplicationUser> userManager)
{
_logger = logger;
this.userManager = userManager;
}
Then inside your action you will be able to get all the claims and rols as desired using the instance of UserManager
within your action you could use something like this
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); // get the user ID
var user = await userManager.FindByIdAsync(userId); // get the user object
var claims = await userManager.GetClaimsAsync(user); // get the claims based on the user object
var rols = await userManager.GetRolesAsync(user); // get the roles based on the user object
I hope this answer your question!
Regards,
Khaled Dehia

Related

Getting user data from Azure AD in a Blazor WASM application

I would like to get not just the name of the authenticated user but username, email data, too.
I use these lines in components but it gives back only the name of user:
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
protected override async Task OnInitializedAsync()
{
var user = (await authenticationStateTask).User;
var name = user.Identity.Name;
...
}
Please.
The user object is of type ClaimsPrincipal. You can find all the claims in the Claims property of the user.
More information:
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-7.0
If there are claims missing, you might be able to add these as optional claims in the app registration.

React SPFX - How to check whether or not a user is a member of a specific Azure Directory

As the title suggests, I am trying to find out whether or not a specific user from my ad group is part of another ad group.
There are plenty of questions that ask the same question but the context of the question differs a lot.
I have read some documentation about on this problem but nothing related on how to get users or groups from your Azure Directory.
For now I am just trying to log out an Azure Directory group or user inside my getFAQ() method. I cannot retrieve some data from my Azure Directory
If someone have an idea on how to accomplish this or could point me in the right direction, it would greatly appreciated.
My provider class looks like this. I have a property called _sp: SPFI that I initialize inside my constructor this._sp = spfi().using(SPFx(context));
A sample of my provider class where I would like to retrieve the data inside my getFAQ() method.
export class FAQProvider implements IFAQProvider {
private _sp: SPFI;
private faqListId: string;
constructor(context: WebPartContext, faqListId: string) {
this._sp = spfi().using(SPFx(context));
this.faqListId = faqListId;
}
public async getFAQ(): Promise<IFAQ[]> {
let result: IFAQ[] = await this._sp.web.lists.getById(this.faqListId).items.expand("FAQ_Category", "FAQ_SubCategory").select("*", "FAQ_Category/Title", "FAQ_SubCategory/Title", "FAQ_Category/CategoryColor", "FAQ_SubCategory/SubCategoryColor")();
let resultFiltered: IFAQ[] = result.map(x => ({
Answer: x.Answer,
FAQ_CategoryId: x.FAQ_CategoryId,
FAQ_SubCategoryId: x.FAQ_SubCategoryId,
Question: x.Question,
FAQ_Category: x.FAQ_Category,
FAQ_SubCategory: x.FAQ_SubCategory,
ID: x.ID,
Audience_Target: x.Audience_Target,
Audience_TargetId: x.Audience_TargetId,
}));
const memberGroup = await this._sp.web.associatedMemberGroup();
console.log(memberGroup);
return resultFiltered;
}
You can use transitiveMemberOf method on users from Your member group.
Hope that helps.

Use Graph inside a multitenant azure function (App credentials)

i 'm working on an azure functions that make some graph call to different tenant (multitenant)
I want to reuse a GraphServiceClient and leveraging token cache
I generate the GraphServiceClient in this way:
List<string> scopes = new List<string>() { "https://graph.microsoft.com/.default" };
var authProvider = ConfidentialClientApplicationBuilder.Create("e9b93362-a788-4644-8623-da9f4d4776a7")
.WithAuthority(AzureCloudInstance.AzurePublic, AadAuthorityAudience.AzureAdMultipleOrgs)
.WithClientSecret("fkpx53225awyQJDHV35:^][")
.Build();
var dd = new MsalAuthenticationProvider(authProvider, scopes.ToArray(),"ugochotmail.onmicrosoft.com");
var appGraphClient = new GraphServiceClient(dd);
Than i should call
authResult = await _clientApplication.AcquireTokenForClient(_scopes)
.WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
.ExecuteAsync();
To obtain a token for the app to access the specific tenant.
The problem is in the authentication provider that is call on every send request but doen't offer a parameter with the tenant name
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var token = await GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
At the moment i just add a property to the Authentication provider to set the tenant. It works but i would like to know if there is a better solution
Per my understanding, it seems your function doesn't allow a parameter which specify the tenant name and then use the tenant name when do GetTokenAsync() method. And now you can just hardcode the tenant name in the line new MsalAuthenticationProvider(... to specify the tenant.
For this problem, I think you can add a variable named tenant in the "Application settings" of your function app (as below screenshot show).
Then add a line of code string tenant = System.Environment.GetEnvironmentVariable("tenant"); above var token = await GetTokenAsync();
After that, you can add parameter in method GetTokenAsync() like GetTokenAsync(tenant). Then you do not need to hardcode tenant name in code, you just need to change the tenant name in "Application settings" of your function.
If I misunderstand your requirement, please provide more details.
=============================Update===============================
It seems you just want to specify the tenant in your code by a parameter, but not add the tenant name as a property in var dd = new MsalAuthenticationProvider(authProvider, scopes.ToArray(),"tenant name");. If so, you can refer to the code below (just add a line .WithTenantId("xxx.onmicrosoft.com") when do ConfidentialClientApplicationBuilder)
No it doesn't fix the problem as, in a multitenant, the target tenant is send as a parameter to the function. I'm working on an other approach i will come back when i will finish tests.
Thanks a lot

Identity Server - Add custom parameters to the JSON response from the Token Endpoint

I’ve a requirement to add custom members in the token response of the Identity Server Token Endpoint.
Sample expected response:
{
"access_token": "XXXXXXXXXXXXXXX",
"token_type": "bearer",
"expires_in": 3600,
"scope": "patient/Observation.read patient/Patient.read",
"patient": 123,
"refresh_token":"XXXXXXXXXXXXXXXXX"
}
I would like to add the scope, patient parameter in the response, even though it's present in the Access Token.
Any guidance on this would be really helpful!
Not possible with out of the box configuration because of the static nature of TokenResponse model.
Having said that, IdentityServer4 is extremely extensible so you could technically create your own implementation of ITokenResponseGenerator and your own custom model for TokenResponse in order to achieve this behaviour.
Would not recommend this, however, because it seems like you are trying to solve the shortcoming of some other system not being able to process a pretty standard JWT.
For Identity Server 4, you can add a custom parameter in the token response by implementing the ICustomTokenRequestValidator interface.
public class CustomTokenRequestValidator : ICustomTokenRequestValidator
{
public Task ValidateAsync(CustomTokenRequestValidationContext context)
{
context.Result.CustomResponse =
new Dictionary<string, object> {{ "patient", "alice"}};
return Task.CompletedTask;
}
public CustomTokenRequestValidator()
{
}
}
Also do not forget to register the dependency in the configureServices method in startup. You can append .AddCustomTokenRequestValidator<>({pass-in-name-of-class-implementing}) after adding the IdentityServer service.
I was able to get the Identity Server 3 to provide a customized Token Response by implementing the ICustomTokenResponseGenerator Interface and adding the required parameters in the Custom section of the tokenResponse.
Steps:
Implement the Interface
Register the Interface in the factory
This fix worked for me and i'm able to get the custom items in the token response.
//Interface Implementation
public class CustomTokenResponseGeneratorService: ICustomTokenResponseGenerator
{
protected ITokenService _tokenService;
public CustomTokenResponseGeneratorService(ITokenService tokenService)
{
_tokenService = tokenService;
}
public Task<TokenResponse> GenerateAsync(ValidatedTokenRequest request, TokenResponse response)
{
var patientID = 123;
response.Custom.Add("patient" , patientID);
response.Custom.Add("scope" , request.AuthorizationCode.Scopes.ToArray());
response.Custom.Add("need_patient_banner" , "false");
response.Custom.Add("encounter" , patientID);
response.Custom.Add("client_id" , request.AuthorizationCode.ClientId);
response.Custom.Add("smart_style_url" , "UNK");
return Task.FromResult(response);
}
}
Step2: Register the CustomTokenResponseGenerator in the Identity Server factory
//Token Service
factory.CustomTokenResponseGenerator = new Registration<ICustomTokenResponseGenerator, CustomTokenResponseGeneratorService>();
Reference: Interface Detail for Identity Server 3

How to handle security/authentication on a DNN-based web API

I am building a REST API for a DotNetNuke 6 website, making use of DNN's MVC-based Services Framework. However, I don't have any background in authentication, so I'm not even sure where to start.
Basically, we want our clients to be able to make GET requests for their portal's data, and we want some clients (but not all) to be able to POST simple updates to their user data.
I've been trying to search for information, but the trouble is I'm not sure what I'm searching for. DNN has different logins and roles, but I'm not sure if or how they factor in. I've heard of things like oAuth but my understanding of it is at the most basic level. I don't know if it's what I need or not and if or how it applies to DNN. Can anyone point me in the right direction?
UPDATE:
Based on the answer below about tying it with a module and further research, here is what I have done:
I created a module just for this service, and I added two special permissions for it: "APIGET" and "APIPOST." I assigned these to some test roles/test accounts in DNN. I wrote a custom authorize attribute that, given the module ID, checks if the current user has the necessary permission (either through roles or directly). As far as I can tell, tab ID is irrelevant in my case.
It appears to be working both with a web browser (based on the DNN account I'm logged into) and with a php script that sends an HTTP request with an account username/password.
The authorize attribute:
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Security;
using DotNetNuke.Security.Permissions;
using System.Web;
public class MyAuthorize : DotNetNuke.Web.Services.AuthorizeAttributeBase
{
public const string AuthModuleFriendlyName = "MyAuthModule";
public const string GETPermission = "APIGET";
public const string POSTPermission = "APIPOST";
public string Permission { get; set; }
protected override bool AuthorizeCore(HttpContextBase context)
{
ModuleController mc = new ModuleController();
ModuleInfo mi = mc.GetModuleByDefinition(PortalController.GetCurrentPortalSettings().PortalId, AuthModuleFriendlyName);
ModulePermissionCollection permCollection = mi.ModulePermissions;
return ModulePermissionController.HasModulePermission(permCollection, Permission);
}
}
The controller:
("mytest" is the endpoint for both GET and POST)
public class MyController : DnnController
{
[ActionName("mytest")]
[AcceptVerbs(HttpVerbs.Get)]
[DnnAuthorize(AllowAnonymous = true)]
[MyAuthorize(Permission = MyAuthorize.GETPermission)]
public string myget(string id = "")
{
return "You have my permission to GET";
}
[ActionName("mytest")]
[AcceptVerbs(HttpVerbs.Post)]
[DnnAuthorize(AllowAnonymous = true)]
[MyAuthorize(Permission = MyAuthorize.POSTPermission)]
public string mypost(string id = "")
{
return "You have my permission to POST";
}
}
The main way that you tie a service in the DNN Services Framework into DNN permissions is to associate the permissions with a module instance. That is, you'll require users of your service to identify which module they're calling from/about (by sending ModuleId and TabId in the request [headers, query-string, cookies, form]), then you can indicate what permissions they need on that module to take a particular action on the service.
You can use the SupportedModules attribute on your service, and pass in a comma-delimited list of module names, to ensure that only your own modules are being allowed. Then, add the DnnModuleAuthorize attribute at the service or individual action level to say what permission the user needs on that module. In your instance, you can also add the AllowAnonymous attribute on the GET actions, and have one DnnModuleAuthorize on the service, for the POST methods (and anything else). Note that you cannot put the AllowAnonymous attribute on the controller; it will override authorizations put at the action, making it impossible to make actions more restrictive.
You'll also want to add the ValidateAntiForgeryToken attribute on the POST actions, to protect against CSRF attacks.
If you don't have a module that naturally associates its permissions with your service, you can create one just for that purpose, solely to expose itself as a permissions management utility.
Once you've figured out the authorization piece above, DNN will take care of authentication using your forms cookie (i.e. AJAX scenarios are taken care of automatically), or via basic or digest authentication (for non-AJAX scenarios). That said, if you're doing non-AJAX, you'll need to figure out a way to validate the anti-forgery token only when it applies.
The Services Framework in DNN is what you are after. It allows you to provide a REST API that plugs directly into DNN security.
Here are some articles to help you get started:
http://www.dotnetnuke.com/Resources/Wiki/Page/Services-Framework-WebAPI.aspx
http://www.dotnetnuke.com/Resources/Blogs/EntryId/3327/Getting-Started-with-DotNetNuke-Services-Framework.aspx
Note, there are some difference in DNN 6 and DNN 7 when using the Services Framework:
http://www.dotnetnuke.com/Resources/Blogs/EntryId/3514/Converting-Services-Framework-MVC-to-WebAPI.aspx
Just wanted to note that the DnnModuleAuthorize attribute takes a PermissionKey parameter for custom permissions so you can do stuff like this:
[DnnModuleAuthorize(PermissionKey = "DELETEDATA")]
[HttpPost]
public HttpResponseMessage DeleteData(FormDataCollection data)
It doesn't look like you can supply your own error message with this so you might to wrap your method body like this instead and leave off the custom permission attribute:
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
[HttpPost]
public HttpResponseMessage DeleteData(FormDataCollection data)
{
var errorMessage = "Could not delete data";
if (ModulePermissionController.HasModulePermission(ActiveModule.ModulePermissions,"DELETEDATA"))
{
// do stuff here
}
else
{
errorMessage = "User does not have delete permission";
}
var error = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content =
new StringContent(
errorMessage)
};
return error;
}
Just wanted to add to #Richards comment for using the [DnnModuleAuthorize(PermissionKey = "DELETEDATA")] for custom permissions.
The full attribute should be:
[DnnModuleAuthorize(PermissionKey = "DELETEDATA", AccessLevel = SecurityAccessLevel.Edit)]
Leaving it blank does nothing as shown here: https://github.com/dnnsoftware/Dnn.Platform/blob/f4a5924c7cc8226cfe79bbc92357ec1a32165ada/DNN%20Platform/Library/Security/Permissions/PermissionProvider.cs#L810
I guess you require a plugin that allows you to construct GET and POST APIs. you can use this plugin I found on the DNN store. https://store.dnnsoftware.com/dnn-rest-api-custom-api-authentication-authorization.

Resources