In Blazor a server app, why is GetAuthenticationStateAsync() returning a different number of claims for Chrome vs MS Edge? - azure-active-directory

We use Azure Active Directory for authentication to a server-side Blazor app (.net6.0). I'm attempting to authorize users to do a specific action on one of the pages. A user should be authorized if they have been added to the correct Active Directory group.
The authorization works fine in Google Chrome, but not in Edge. Same user account both times.
Here's a simplified version of what I'm doing:
#page "/accounting"
#layout GeneralLayout
#inject AuthenticationStateProvider AuthStateProvider
#inject IAuthorizationService AuthService
<PageBody>
#if (IsVendorExpenseAccessEnabled) {
#* show vendor expense things *#
}
else
{
#* don't show vendor expense things *#
}
</PageBody>
#code {
private AuthorizationResult HasFullAccessResult { get; set; }
private bool IsVendorExpenseAccessEnabled =>
HasFullAccessResult != null && HasFullAccessResult.Succeeded;
protected override async Task OnInitializedAsync() {
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
HasFullAccessResult = await AuthService.AuthorizeAsync(authState?.User, "MY POLICY THAT CHECKS GROUP CLAIMS");
}
}
When I debug this while using Chrome, authState.User.Claims has 14 group claims. When I debug while using Edge, authState.User.Claims has 12 group claims. One of those claims is the group this user needs in order to utilize page functionality.
Why are claims missing for a user when they use this page in Edge?
Update
Seems the issue in Edge may have been a red herring. I logged out my App in Chrome then logged back in, and now these claims are missing in Chrome, too. I'm thinking it most likely has something to do with our Azure AD configuration. I overheard IT was working in there today.

It turns out that our IT department did a full sync of our On-Prem AD and Azure AD. I'm not 100% sure of the details, but something caused all of the Object IDs for the groups I was authorizing against to change. Once I realized this, I was able to update my code to authorize against these new IDs. My app's pages are now working.

Related

Azure AD logout issue in multiple browser tabs

I have followed Microsoft website sample to setup a web authentication via Azure AD. I have tested the login via Azure AD, and it was able to logged in and out system. But, I noticed there was an issue when logging out from system. When I duplicate for multiple tabs in browser(chrome, firefox), and logout from one of the duplicated tabs. But other duplicated tabs, still remain logged in status even I refresh them. But when I open a new tab to access again, it required me to login. Below is my code for logout, does anyone know what I have missed out?
protected async Task OnLogout()
{
if (await DialogService.Confirm("Are you sure want to logout?", "Logout Confirmation",
new ConfirmOptions() { OkButtonText = "Yes", CancelButtonText = "No" }) == true)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}

Connecting Blazor Wasm to Identity Server 4 - Register and account management links not working

Hopefully someone can guide me in the right direction, because I've been working on this for a while now.
I've create a Blazor WASM hosted in .Net Core. However instead of using the identity in the Server (API) project, I wanted to use for authentication an Identity Server hosted in a different project (so that I can use a standalone Identity Server).
I've create an Identity Server 4 project and also scaffold-ed the Identity Razor pages (so that I have the full flow with registration, account management, password recovery etc.) instead of the basic 3 or so pages that Identity Server generates.
In my Blazor Client project I've added the following inside the Main method:
{
// Bind to the oidc section in the appsettings.
builder.Configuration.Bind("oidc", options.ProviderOptions);
options.UserOptions.RoleClaim = JwtClaimTypes.Role;
})
Also, in my appsettings file I have the following oidc section:
"oidc": {
"Authority": "https://localhost:5001",
"ClientId": "ProjectAllocation.Client",
"DefaultScopes": [
"openid",
"profile",
"roles",
"offline_access"
],
"PostLogoutRedirectUri": "/",
"ResponseType": "code"
}
}
In Identity Server, in the Startup ConfigureServices I am redirecting the Login \ Logout to use the pages in the scaffolded Identity Area:
var builder = services.AddIdentityServer(options =>
{
...
options.UserInteraction.LoginUrl = "/Identity/Account/Login";
options.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
options.Authentication = new IdentityServer4.Configuration.AuthenticationOptions
{
CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
CookieSlidingExpiration = true
};
})
Now the login and logout work and it seems I am getting the right token data in the client; I haven't implemented the API on the server side yet.
My problem is that the register and the account management links from the Client project are not working. If you would use the template from the VS with integrated identity server then you would be able to click on the register link inside the client app and be taken to the Account\Register page in the Identity area; also once you logged in you can click on the "Hello ...." link and be taken to the Account management in the Identity area.
However this doesn't work in my case. If I navigate from the browser directly to those areas then it works (i.e.: browse to https://localhost:5001/Identity/Account/Register: it works).
When I click on Register button in the Client app it just reloads the app with the following link:
https://localhost:44395/?returnUrl=%2Fauthentication%2Flogin : it looks as if the app is being asked to login, even though the Register page in the Identity Server is marked to allow anonymous access.
I am really puzzled as to why this doesn't work. I can't figure out which settings in the Blazor project sets the links to navigate to via the RemoteAuthenticatorView. I am considering replacing the register button so that it doesn't navigate via the RemoteAuthenticatorView anymore and instead it uses a regular link directly to the Identity Server Register page, but I am not sure what the implications are; also it's really annoying that I cannot get this to work properly.
I've even tried to change the path to the register page so that instead of Identity/Account/Register is Account/Register via the ConfigureServices in the Startup file in the Identity Server 4:
services.AddRazorPages(options => {
options.Conventions.AddAreaPageRoute("Identity", "/Account/Register", "Account/Register");
});
which works from the browser (https://localhost:5001/Account/Register), but it still didn't work from the WASM Blazor Client.
Any ideas?
Thanks,
Raz
I am leaving this here in case someone else stumbles upon this.
I've looked through the Blazor WASM code and for registration and account management it uses the NavigationManager to navigate to the paths supplied in RemoteRegisterPath and RemoteProfilePath properties of the AuthenticationPaths.
For some strange reason though it looks like you cannot navigate to an outside url: the NavigationManager will ignore the supplied base address and use the base address of the project. So even though I've tried to provide an address like https://localhost:5001/Identity/Account/Register, the application actually navigates to https://localhost:44395/Identity/Account/Register.
As a workaround I've created a controller called Account with two methods Register and Manage which will redirect to the address of the Identity Server. So the Blazor client will call the corresponding method in the Account controller in the Blazor API server project which will redirect to the corresponding Identity Server page.
I've modified the call to AddOidcAuthentication() in the Main method inside the Blazor Client project:
builder.Services.AddOidcAuthentication(options =>
{
// Bind to the oidc section in the appsettings.
builder.Configuration.Bind("oidc", options.ProviderOptions);
options.AuthenticationPaths.RemoteRegisterPath = "Account/Register";
options.AuthenticationPaths.RemoteProfilePath = "Account/Manage";
options.UserOptions.RoleClaim = JwtClaimTypes.Role;
})
I've also created an AccountController in the Controllers folder in the Blazor API server project:
[Route("[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
[AllowAnonymous]
[HttpGet("Register")]
public IActionResult Register(string returnUrl)
{
return Redirect(#"https://localhost:5001/Identity/Account/Register?returnUrl=https://localhost:44395/" + returnUrl);
}
[AllowAnonymous]
[HttpGet("Manage")]
public IActionResult Manage(string returnUrl)
{
return Redirect(#"https://localhost:5001/Identity/Account/Manage?returnUrl=https://localhost:44395/" + returnUrl);
}
}
Might be a good idea to authorize the Manage method. Also the returnUrl would probably need some additional parameters (at least for the login \ logout there are more parameters).
In addition, I needed to make some small changes in the Identity files scaffolded in the Identity Server project to allow redirection to non-local paths.
There are certain things that can be improved and it does feel like a hack, but the solution works for now and I couldn't find any better alternatives.
The standard IdentityServer4 project templates does not include any pages for registering users, these page you have to develop your self inside IdentityServer4. To get user registration pages you can try one of these projects/products:
An ASP.NET Core IdentityServer4 Identity Template with Bootstrap 4 and Localization
AdminUI

NET Core 3.1 Microsoft.Identity.Web Role Based Authorization Issue

I'm using an MS Code example from GitHub "Azure-Samples
/
active-directory-aspnetcore-webapp-openidconnect-v2" to connect a .net Core 3.1 webapp to a single tenant in Azure AD.
The Micorsoft employee who's maintained this code sample did a recent webinar on 25th June 2020 where he did a high level overview in utilizing AppRoles for Roles based authorization in net core. The image below shows the code sample shown from his presentation which is using an older NuGet library 'Microsoft.AspNetCore.Authentication.AzureAD.UI' for managing the login.
HOWEVER in the sample project code he used on GitHub, he's used the newer 'Microsoft.Identity.Web' library which does not appear to have any code section where I extract the roles claims from the token received back from Azure following a successful login authentication.
For Ref: the presentation from UTUBE - Title = Implement Authorization in your Applications with Microsoft identity platform-June 2020
For Ref: The MS code sample project = https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/1-WebApp-OIDC
Code sample below:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
// Handling SameSite cookie according to https://learn.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1
options.HandleSameSiteCookieCompatibility();
});
// **THIS ONE LINER HAS REPLACED THE FORMER CODE SECTION FROM THE OLDER LIBRARY**
// Sign-in users with the Microsoft identity platform
services.AddSignIn(Configuration);
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages();
}
I want to user Role Based access control on my MVC Controllers and Razor pages by decorating the methods with [Authorize(Roles = "ViewLogs")] but when I've logged in test the page, I get ACCESS DENIED so the there is some required code missing somewhere and i dont know what or where to add the required code to get this working.
I have verified that I am receiving the Role "ViewLogs" successfully within the token received back from Azure after logging in, its just there is something vital missing here that .NET Core needs to in order to define the Roles policy check from the claims in the token.
Image below shows the Debug of the token contents:
Here is the official ms sample for using roles with msal in .net core
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/5-WebApp-AuthZ/5-1-Roles#support-in-aspnet-core-middleware-libraries
It maps the roles claim to policies and groups, then it authorizes using the policy, however, I believe you can still authorize using roles or groups.
it also uses microsoft.identity.web (msal)
Check the HttpContext.User.Claims collection to ensure that the roles claim are present,
[Edit]it seems they are.
Make sure the line app.UseAuthorization(); is present in Startup.cs in the Configure() in the right order.
Remove the [Authorize] attribute from the controller actions and execute the HttpContext.User.IsInRole() method in those actions to check the roles being acted upon as expected.
As advised by the asp.net core team, the sample advises using the new Policy-based authorization in ASP.NET Core
if all the above do not work then create the project again using the steps provided here. Note that .

Delete User from Azure AD B2C using Graph API right after it was created - ObjectNotFoundException

I'm using Custom Policy to sign up users in Azure AD B2C.
In the last step, before the JWT is issue, the technical profile does the following:
<!-- Store the user in the AD -->
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" ContinueOnError="false" />
<!-- Sends the user information, including ObjectId to website to store locally-->
<ValidationTechnicalProfile ReferenceId="REST-SendUserInformation" ContinueOnError="false"/>
So, this works fine. However, in my code, if something goes wrong in the step 2 (send the user's information to store locally), I call the Graph API to delete the user that was just created.
However, I noticed that the Graph API works when I "debug" (which means, taking some time to click next.. next...), but when it's running in the server, the user is not deleted from the AD.
It doesn't throw an exception.
My code to delete the user is the following:
public AzureGraphService(IConfiguration configuration)
{
var azureOptions = new AzureAdOptions();
configuration.Bind("AzureAdB2C", azureOptions);
// Client credential provider is used by services and desktop applications to acquire Microsoft Graph access token without a user.
_confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(azureOptions.ClientId)
.WithTenantId(azureOptions.Domain)
.WithClientSecret(azureOptions.ClientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(_confidentialClientApplication);
// Set up the Microsoft Graph service client with client credentials
_graphClient = new GraphServiceClient(authProvider);
}
public async Task DeleteUserFromAD(string azuresubid)
{
try
{
// Delete user by object ID
await _graphClient.Users[azuresubid]
.Request()
.DeleteAsync();
}
catch (Exception ex)
{
// TODO - Log exception?
throw ex;
}
}
After looking the Audit Logs in Azure AD B2C, I found this
"ObjectNotFoundException".
However, I'm certain that the object is correct and the objectID that I use to delete is also correct because it works if I "debug" (go slowly).
My question is:
Is there a delay between the Object (User) is created in the AD B2C and the time that I can actually see and delete the object from there?
Thank you
There is a delay, the policy execution targets the same DCs, your graph api call from your REST API is likely hitting a different DC and cannot find the account at that time. Use retry logic, try 3 times with a 5 second delay. Usually replication completes in 7sec.

Signing up new AAD B2C users with various emails to LiveID automatically?

I have a .Net Core 2.0 Web App that connects to Azure Graph, authenticates you via OpenID and adds new local accounts to the tenant. My issue is that when I add those new users, they might or might not have their various emails registered on microsoft, LiveID. Is there maybe a way of binding their emails to microsoft in case they're not already registered? In case this is not clear enough, have an example.
In case this is not clear enough, have an example :
I add a local user with those parameters to my tenant
{
"accountEnabled":true,
"creationType":"LocalAccount",
"displayName":"LccotCh222602",
"passwordProfile":{
"password":"Ar3g345%#3ha",
"forceChangePasswordNextLogin":false
},
"signInNames":[
{
"type":"userName",
"value":"TestAccount"
},
{
"type":"emailAddress",
"value":"TestAccount#gmail.com"
}
]
}
And now when I try to log in to my app here (image reference) the user is not found because he hasn't registered the TestAccount#gmail.com email in Microsoft yet.
I'd like to either add that email to Microsoft automatically or find a way around it. Any tips will be much appreciated.
The new register account should work for Azure AD B2C. Here are the steps which works well for me:
1.Register the account using the REST below:
POST:https://graph.windows.net/adb2cfei.onmicrosoft.com/users?api-version=1.6
authorization: bearer {access_token}
{
"accountEnabled":true,
"creationType":"LocalAccount",
"displayName":"LccotCh222602",
"passwordProfile":{
"password":"Ar3g345%#3ha",
"forceChangePasswordNextLogin":false
},
"signInNames":[
{
"type":"userName",
"value":"TestAccount"
},
{
"type":"emailAddress",
"value":"TestAccount#gmail.com"
}
]
}
2.Login with the new register account(TestAccount#gmail.com/Ar3g345%#3ha)
https://login.microsoftonline.com/ADB2CFei.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_B2C_SignUpOrSignIn&client_id=420a3a24-97cf-46ca-a882-f6c047b0d845&nonce=defaultNonce&redirect_uri=https%3A%2F%2Flocalhost%3A44316%2F&scope=openid&response_type=id_token&prompt=login
And to sign-in the local account with Email, please ensure that you have enable the email sign-in like below and sign-in the app with the policy:

Resources