umbraco / Umbraco.Cloud.Issues

Public issue tracker for Umbraco Cloud
26 stars 2 forks source link

Make Umbraco ID work with other external login providers #583

Closed skttl closed 2 months ago

skttl commented 2 years ago

Issue description

Apparently you can't add external backoffice login providers when also having Umbraco ID.

When signing in using another external backoffice login provider you get the message "Unable to unprotect the message.State". Weird thing is, the message is shown in the Umbraco ID button element, and not the other provider.

image

I've added the provider to Startup.cs like this:

                .AddBackOfficeExternalLogins(extLoginBuilder =>
                {
                    extLoginBuilder.AddBackOfficeLogin(
                         auth =>
                         {
                             auth.AddMicrosoftIdentityWebApp(options =>
                             {
                                 var tenantId = "[tenant id here]";
                                 var clientId = "[client id here]";
                                 var instance = "https://login.microsoftonline.com/";
                                 options.CallbackPath = "/umbraco-signin-oidc";
                                 options.Instance = instance;
                                 options.TenantId = tenantId;
                                 options.ClientId = clientId;
                                 options.SignedOutRedirectUri = "/umbraco";

                                 // Preferred over IClaimsTransformation which runs for every AuthenticateAsync
                                 options.Events.OnTokenValidated = ctx =>
                                 {
                                     var username = ctx.Principal?.Claims.FirstOrDefault(c => c.Type == ClaimConstants.PreferredUserName);
                                     if (username != null && ctx.Principal?.Identity is ClaimsIdentity claimsIdentity)
                                     {
                                         claimsIdentity.AddClaim(
                                             new Claim(
                                                 ClaimTypes.Email,
                                                 username.Value
                                             )
                                         );
                                     }

                                     return Task.CompletedTask;
                                 };
                             },
                             openIdConnectScheme: auth.SchemeForBackOffice(Constants.AzureAd)); // also tried a custom scheme like $"{Constants.AzureAd}-skttl";
                         }
                         );
                })

If I remove <PackageReference Include="Umbraco.Cloud.Identity.Cms" Version="4.0.51" /> from my csproj (and add <PackageReference Include="Microsoft.Identity.Web" Version="1.16.0" />), it works as expected, but then I don't have Umbraco ID, which I guess is essential for HQ support reasons?

sajumb commented 2 years ago

Thanks, great post. We will add this to out product backlog and consider it in more detail after Codegarden.

a-karandashov commented 2 years ago

@skttl I've tried to setup external auth provider for backoffice with v9 in Umbraco Cloud and everything was fine.

skttl commented 2 years ago

@AndreyKarandashovUKAD which provider did you use? I used azure ad, which I think umbraco id is based on. That might be the problem.

a-karandashov commented 2 years ago

@skttl It was Azure AD, but I was using OpenIdConnect protocol

a-karandashov commented 2 years ago

@skttl and do not forget to disable auto-redirect for Umbraco ID if you want to use 2 external providers in parallel. (you don't have auto redirect for local environment, but it's enabled for dev/stage/prod envs. You can add this to umbraco-cloud.json in "Identity" object definition. "AutoRedirectLogin": false

AaronSadlerUK commented 4 months ago

@skttl Did you manage to get this working?

Could you provide any examples, thanks!

skttl commented 4 months ago

Hi @AaronSadlerUK

Heres a custom example:

.AddBackOfficeExternalLogins(extLoginBuilder =>
                {
                    extLoginBuilder.AddBackOfficeLogin(
                        auth =>
                        {
                            auth.AddMicrosoftIdentityWebApp(options =>
                            {
                                var tenantId = ""; // TODO
                                var clientId =  ""; // TODO
                                var instance = ""; // TODO

                                options.CallbackPath = "/umbraco-custom-signin-oidc"; // note, not using /umbraco-signin-oidc, as Cloud Identity uses that
                                options.Instance = instance;
                                options.TenantId = tenantId;
                                options.ClientId = clientId;
                                options.SignedOutRedirectUri = "/umbraco";

                                // Preferred over IClaimsTransformation which runs for every AuthenticateAsync
                                options.Events.OnTokenValidated = ctx =>
                                {
                                    var username = ctx.Principal?.Claims.FirstOrDefault(c => c.Type == ClaimConstants.PreferredUserName);
                                    if (username != null && ctx.Principal?.Identity is ClaimsIdentity claimsIdentity)
                                    {
                                        claimsIdentity.AddClaim(
                                             new Claim(
                                                 ClaimTypes.Email,
                                                 username.Value
                                             )
                                         );
                                    }

                                    return Task.CompletedTask;
                                };
                            },
                            openIdConnectScheme: auth.SchemeForBackOffice(Constants.AzureAd));
                        },
                        options =>
                        {
                            options.ButtonStyle = "btn-microsoft";
                            options.Icon = "icon-favorite";

                            options.AutoRedirectLoginToExternalProvider = false;

                            options.AutoLinkOptions = new ExternalSignInAutoLinkOptions(
                                autoLinkExternalAccount: false,
                                defaultUserGroups: new[] { "editor" },
                                allowManualLinking: true)
                            {
                                OnAutoLinking = (autoLinkUser, loginInfo) =>
                                {
                                    autoLinkUser.IsApproved = true;
                                },
                                OnExternalLogin = (user, loginInfo) =>
                                {
                                    return true;
                                },
                            };
                        }
                        );
                })

But after that, I've switched to just using the Umbraco.Community.AzureSSO package. Note, you can't use the latest version (1.3.x) of it, as it depends on a newer version of Microsoft.Identity.Web, which is not supported by Cloud Identity.

AaronSadlerUK commented 4 months ago

Ah ok, so it can be used alongside the existing setup I use for non cloud sites... Sounds great!

ksuvec commented 2 months ago

Hi @skttl

We will start working on supporting external login providers out of the box for Umbraco Cloud this summer!