[SOLVED] Need Help with Microsoft Entra ID Authent...
# help-with-umbraco
o
Hi everyone, I’m trying to implement Microsoft Entra ID Authentication for Members in an Umbraco CMS project following this official guide: [Add Microsoft Entra ID Authentication.](https://docs.umbraco.com/umbraco-cms/13.latest/tutorials/add-microsoft-entra-id-authentication?fallback=true) Here’s my setup: - Umbraco Version: 13.5.2 (.NET 8) - uSkinned Site Builder: 6.1.2 (though I believe it shouldn’t matter here). What I’ve done so far:
1. Created the Code Files as per the Guide EntraIDB2CMembersExternalLoginProviderOptions.cs:
Copy code
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Security;

namespace HunzikerIntranet.HQM.Umbraco.Security;

public class EntraIDB2CMembersExternalLoginProviderOptions : IConfigureNamedOptions<MemberExternalLoginProviderOptions>
{
    public const string SchemeName = "ActiveDirectoryB2C";

    public void Configure(string? name, MemberExternalLoginProviderOptions options)
    {
        if (name != Constants.Security.MemberExternalAuthenticationTypePrefix + SchemeName)
        {
            return;
        }

        Configure(options);
    }

    public void Configure(MemberExternalLoginProviderOptions options)
    {
        options.AutoLinkOptions = new MemberExternalSignInAutoLinkOptions(
            autoLinkExternalAccount: true,
            defaultCulture: null,
            defaultIsApproved: true,
            defaultMemberTypeAlias: Constants.Security.DefaultMemberTypeAlias
        )
        {
            OnAutoLinking = (autoLinkUser, loginInfo) =>
            {
                // Customize Member creation if necessary.
            },
            OnExternalLogin = (user, loginInfo) =>
            {
                return true;
            }
        };
    }
}
MemberAuthenticationExtensions.cs:
Copy code
namespace HunzikerIntranet.HQM.Umbraco.Security;

public static class MemberAuthenticationExtensions
{
    public static IUmbracoBuilder ConfigureAuthenticationMembers(this IUmbracoBuilder builder)
    {
        var config = builder.Services.BuildServiceProvider().GetRequiredService<IConfiguration>();
        var azureAdOptions = config.GetSection("AzureAd").Get<AzureAdOptions>();

        builder.Services.ConfigureOptions<EntraIDB2CMembersExternalLoginProviderOptions>();

        builder.AddMemberExternalLogins(logins =>
        {
            logins.AddMemberLogin(membersAuthenticationBuilder =>
            {
                membersAuthenticationBuilder.AddMicrosoftAccount(
                    membersAuthenticationBuilder.SchemeForMembers(
                        EntraIDB2CMembersExternalLoginProviderOptions.SchemeName
                    ),
                    options =>
                    {
                        options.ClientId = azureAdOptions.ClientId;
                        options.ClientSecret = azureAdOptions.ClientSecret;
                        options.CallbackPath = azureAdOptions.CallbackPath;

                        options.TokenEndpoint = $"{azureAdOptions.Instance}{azureAdOptions.TenantId}/oauth2/v2.0/token";
                        options.AuthorizationEndpoint = $"{azureAdOptions.Instance}{azureAdOptions.TenantId}/oauth2/v2.0/authorize";

                        options.SaveTokens = true;
                    }
                );
            });
        });

        return builder;
    }
}

public class AzureAdOptions
{
    public string Instance { get; set; }
    public string TenantId { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
    public string CallbackPath { get; set; }
}
Updated Program.cs:
Copy code
builder.CreateUmbracoBuilder()
    .AddBackOffice()
    .AddWebsite()
    .AddDeliveryApi()
    .AddComposers()
    .ConfigureAuthenticationMembers() // Added this
    .Build();
2. Updated appsettings.json:
Copy code
"AzureAd": {
  "Instance": "https://login.microsoftonline.com/",
  "TenantId": "YOUR-TENANT-ID",
  "ClientId": "YOUR-CLIENT-ID",
  "ClientSecret": "YOUR-CLIENT-SECRET",
  "CallbackPath": "/umbraco-b2c-members-signin"
}
3. Registered the Redirect URI in Azure In Azure, under App Registrations, I registered the following Redirect URI:
Copy code
https://localhost:44367/umbraco-b2c-members-signin
The Problem: When I get redirected to the callback URL (/umbraco-b2c-members-signin), I see the following error:
Copy code
The oauth state was missing or invalid.
The full exception trace mentions:
Copy code
Microsoft.AspNetCore.Authentication.AuthenticationFailureException: The oauth state was missing or invalid.
Suspicions: I suspect the issue might be related to: - The App Registration in Azure: Perhaps I’ve missed something while setting it up. - The OAuth State Parameter: Something seems wrong with how the state is being generated or maintained. My Goal: - To allow members from my company (identified by our domain name, e.g., @mycompany.com) to log in using Microsoft Entra ID. - Once logged in, the member should be auto-created in Umbraco (if not already present) and granted access to protected member areas.
Any guidance or suggestions would be greatly appreciated. Thanks in advance! 😊
k
What is the URL when you get to Entra B2C? What is the state and the return URL?
If your Entra App Redirect URI was wrong, Entra B2C would complain.
Did you setup a single tenant app registration in B2C?
Is the resulting
TokenEndpoint
and
AuthorizationEndpoint
correct? appSettings => interpolated string etc.
d
Just FYI, there is a issue with Entra ID in Umbraco 13.5.2 which will be fixed in 13.6.0. Don't know if's the issue or if it's relevant here though. https://github.com/umbraco/Umbraco-CMS/issues/17383
k
17383 looks quite user-specific.
I don't understand these comments from the Member Auth Options in the Umbraco tutorial: // Callbackpath: Represents the URL to which the browser should be redirected to. // The default value is /signin-oidc. // This needs to be unique. options.CallbackPath = "/umbraco-b2c-members-signin"; Is this just accidental copy-paste from some ASP.NET OIDC guide?
umbraco-b2c-members-signin
looks like it's handled internally by Umbraco? Or does Umbraco register this callback path route and connect it to these Member Auth Options?
o
I think that's for the backoffice, I need it for the members on my website.
I really don't know, I need to look at that. It's so complicated to get a key and all those IDs
k
It's radio button choice on the app reg. Single, any work/school/personal, any personal IIRC.
o
Okay, I can register a new app and show you all the steps.
I guess choose "Web" and then my URL: https://localhost:44367/umbraco-aad-members-signin
k
Didn't you put
https://localhost:44367/umbraco-b2c-members-signin
? To match the callback URL in the member auth options.
o
I am gonna change it
It says I can choose everything in the docs.
Now I got the Client ID, Tenant ID. And I need a key. But I need to generate it first and configurate things there
k
Well, the redirect URI is where the code will be sent. There needs to be something on the Umbraco side listening to that URI and I believe that's what the callback path in the member auth options is for.
Not sure what key you mean. The client secret?
o
Yes
The client secret
k
If it still doesn't work after you're done, please show the URL when you get to Entra B2C, the state and the return URL. It should be https://login.microsoft.com/... or your custom Entra URL. And also the URL that gives the "invalid oauth state" message. It should be https://localhost:44367/umbraco-b2c-members-signin?code=x&...
o
Do I need to configure anything else in Azure Portal? I just generated a new secret now and I only had to give it a name and expiry date
Any scopes somewhere maybe?
k
Don't think so. Just verify the tenant and client IDs in your appsettings.
o
Okay, I'll do that. I'll message you in 2-3 minutes
Is that correct: "Instance": "https://login.microsoftonline.com/",
@kdx-perbol should I rename that: public const string SchemeName = "ActiveDirectory";
It was like that "ActiveDirectoryB2C"
But it's not B2C right? Single tenant now
k
Are you using Azure Entra B2C? Or you just have a tenant?
The scheme name shouldn't matter.
The guide doesn't say you should configure an instance URL. Where did you get that? I know it's used for non-Umbraco Azure OIDC, but still.
for the link
I am copying now the docs 1:1 and write the IDs directly in the file, and later after it works, I'll do it with appsettings
I see that
Copy code
namespace MyApp;

public static class MemberAuthenticationExtensions
{
    public static IUmbracoBuilder ConfigureAuthenticationMembers(this IUmbracoBuilder builder)
    {
        builder.Services.ConfigureOptions<EntraIDB2CMembersExternalLoginProviderOptions>();
        builder.AddMemberExternalLogins(logins =>
        {
            builder.Services.ConfigureOptions<EntraIDB2CMembersExternalLoginProviderOptions>();
            builder.AddMemberExternalLogins(logins =>
            {
                logins.AddMemberLogin(
                    membersAuthenticationBuilder =>
                    {
                        membersAuthenticationBuilder.AddMicrosoftAccount(

                            membersAuthenticationBuilder.SchemeForMembers(EntraIDB2CMembersExternalLoginProviderOptions.SchemeName),
                            options =>
                            {
                                options.CallbackPath = "/umbraco-aad-members-signin";

                                options.ClientId = "5censoredd3f";
                                options.ClientSecret = "ZycensoredGG";

                                options.TokenEndpoint = $"https://login.microsoftonline.com/53acensored05c9/oauth2/v2.0/token";
                                options.AuthorizationEndpoint = $"https://login.microsoftonline.com/53a28censored05c9/oauth2/v2.0/authorize";

                                options.SaveTokens = true;
                            });
                    });
            });
        });
        return builder;
    }
}
Copy code
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Web.Common.Security;

namespace MyApp;

public class EntraIDB2CMembersExternalLoginProviderOptions : IConfigureNamedOptions<MemberExternalLoginProviderOptions>
{
    public const string SchemeName = "ActiveDirectory";

    public void Configure(string? name, MemberExternalLoginProviderOptions options)
    {
        if (name != Constants.Security.MemberExternalAuthenticationTypePrefix + SchemeName)
        {
            return;
        }

        Configure(options);
    }

    public void Configure(MemberExternalLoginProviderOptions options)
    {
        options.AutoLinkOptions = new MemberExternalSignInAutoLinkOptions(

            autoLinkExternalAccount: true,

            defaultCulture: null,

            defaultIsApproved: true,

            defaultMemberTypeAlias: Constants.Security.DefaultMemberTypeAlias

        )
        {
            OnAutoLinking = (autoLinkUser, loginInfo) =>
            {
            },
            OnExternalLogin = (user, loginInfo) =>
            {
                return true;
            }
        };
    }
}
The docs must be incorrect in a way... For example, the "MemberAuthenticationExtensions.cs"-File is also wrong and has errors: Try copying that and paste it in your IDE https://cdn.discordapp.com/attachments/1331236554802200586/1331289640510034030/image.png?ex=67911398&is=678fc218&hm=a7203a0af1f53807f7e088dbdbff82ec8cf03f0e706d1622645965675197aa22&
k
Please show the URL when you get to Entra B2C, the state and the return URL. It should be https://login.microsoft.com/... or your custom Entra URL. And also the URL that gives the "invalid oauth state" message. It should be https://localhost:44367/umbraco-b2c-members-signin?code=x&... I mean showing the actual URL, not what the screen looks like on that URL.
What errors are you seeing? And what did you do to fix them? Sure you're not missing some usings/nugets?
o
I don't get any ?code param? I mean I go to the route /umbraco-aad-members-signin and then I get the error I sent you "invalid oauth state".
I am not getting redirected to any https://login.microsoft.com site?
I mean I understand it like that: when I go to "https://localhost:44367/umbraco-aad-members-signin" then I will get redirected to a microsoft page to login. But thats not happening, cause I am getting an error when I visit "https://localhost:44367/umbraco-aad-members-signin". Do I understand something wrong?
I did this: - Removed duplicate registrations for ConfigureOptions and AddMemberExternalLogins. - Simplified and reduced the nesting to fix the errors.
k
What exactly happens when you click on the "SIgn in with Microsoft" button in the screenshot in the bottom of the tutorial?
o
I don't get this button?
How do I add this button?
It wasn't in the tutorial how to add it. I just thought it's a button with a link to your custom route, for me "https://localhost:44367/umbraco-aad-members-signin"
I created my own component for a blocklist:
Copy code
@inherits UmbracoViewPage<SiteBuilderBlock>
@using USNSiteBuilder.Core.Models
@using USNSiteBuilder.Core.Interfaces
@inject ISiteBuilderService _siteBuilderService
@{
    //Available as Component Block, Split Component Block and Pod Block types

    var content = (MicrosoftLogin)Model.BlockContent;
    var settings = (MicrosoftLoginBlockSettings)Model.BlockSettings;
    var externalLoginUrl = Url.Content("~/umbraco-aad-members-signin?scheme=ActiveDirectory");

    <div class="login-microsoft">
        <a class="btn btn-primary" href="@externalLoginUrl">
            Sign in with Microsoft
        </a>
    </div>
}
Just to get to the route
s
you need to wrap the button in a exterlogin form (took us ages to figure out) @foreach (var login in await memberExternalLoginProviders.GetMemberProvidersAsync()) { @using (Html.BeginUmbracoForm(nameof(UmbExternalLoginController.ExternalLogin))) { Sign in with @login.AuthenticationScheme.DisplayName if (externalSignInError?.AuthenticationType == login.ExternalLoginProvider.AuthenticationType) { @Html.DisplayFor(x => externalSignInError.Errors); } } } took this snippet from the exellent example at https://github.com/jbreuer/Umbraco-OpenIdConnect-Example/blob/main/Umbraco-OpenIdConnect-Example.Web/Views/MacroPartials/Login.cshtml
submitted the form will refirect you to the external login. It then handles the logging in of the linked umbraco member. As Entra is an openIdConnect implementation with just some different callbacks/parameters, this helped us a lot to figure it out.
o
omg, thank you so much man! it really did work!
s
var externalSignInError = ViewData.GetExternalSignInProviderErrors();
o
thank you sooo much!!! appreciate it
@kdx-perbol thank u aswell for helping!!
k
The documentation should 100% include the
memberExternalLoginProviders.GetMemberProvidersAsync
stuff. I'd say the guide is pretty useless without it. Aside from the other stuff in there that's confusing. I know, submit an issue, etc. 🙂
s
I will submit an issue, maybe even draft something as a pull request or something as this is really a missing piece
34 Views