umbraco / Umbraco.Forms.Issues

Public issue tracker for Umbraco Forms
29 stars 0 forks source link

Umbraco Form doesn't work with Okta #1070

Closed TheK272 closed 1 year ago

TheK272 commented 1 year ago

We have an Umbraco site that also uses Okta for user management. We have several Umbraco Forms on the site, that work just fine, as long has you haven't logged in. After logging in, pages with Forms on them error out and don't load. Here is the error message:

Unable to convert user ID (00u3j5olob8akHODo1d7)to int using InvariantCulture at Umbraco.Cms.Core.Security.UmbracoUserStore2.UserIdToInt(String userId)

I pasted the whole call stack below. Most of it is Umbraco methods calling each other. The last bit of my code that runs before the error is this: @await Component.InvokeAsync("RenderFormScripts", new {formId = form, theme = "Facs"})

I looked at the methods in the callstack and figured out why its erroring, I just don't know how to fix it. After signing in with Okta, the Okta user Id is put into the httpContext for the user. The claims principal it uses is "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"

Then when Umbraco tries to load the form, it calls "IsMemberAuthorized()". This code attempts to get the User information by pulling the user id from the same claims principal that Okta is using. So it is expecting to get an integer representing the Umbraco user Id, but instead it gets the users Okta ID. It then tries to convert that Id to an int and fails.

Obviously I can't just rip Okta out of our site, but replacing Umbraco Forms would be a lot of work also. Is there any way for me to get around this issue? Any help would be appreciated, I have no idea what to do.

System.InvalidOperationException: Unable to convert user ID (00u3j5olob8akHODo1d7)to int using InvariantCulture at Umbraco.Cms.Core.Security.UmbracoUserStore2.UserIdToInt(String userId) at Umbraco.Cms.Core.Security.MemberUserStore.FindUserAsync(String userId, CancellationToken cancellationToken) at Umbraco.Cms.Core.Security.UmbracoUserStore2.FindByIdAsync(String userId, CancellationToken cancellationToken) at Microsoft.AspNetCore.Identity.UserManager1.FindByIdAsync(String userId) at Microsoft.AspNetCore.Identity.UserManager1.GetUserAsync(ClaimsPrincipal principal) at Umbraco.Cms.Web.Common.Security.MemberManager.GetCurrentMemberAsync() at Umbraco.Cms.Web.Common.Security.MemberManager.IsMemberAuthorizedAsync(IEnumerable1 allowTypes, IEnumerable1 allowGroups, IEnumerable1 allowMembers) at Umbraco.Forms.Web.Services.FormRenderingService.GetFormModelAsync(HttpContext httpContext, Guid formId, Nullable1 recordId, String theme) at Umbraco.Forms.Web.ViewComponents.RenderFormScriptsViewComponent.InvokeAsync(Guid formId, String theme) at Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentInvoker.InvokeAsyncCore(ObjectMethodExecutor executor, Object component, ViewComponentContext context) at Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentInvoker.InvokeAsync(ViewComponentContext context) at Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentInvoker.InvokeAsync(ViewComponentContext context) at Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentHelper.InvokeCoreAsync(ViewComponentDescriptor descriptor, Object arguments) at AspNetCore.ViewsLayout.1() in C:\Code\Umbraco\Facs.Web.UI\Views\Layout.cshtml:line 42 at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync() at AspNetCore.ViewsLayout.ExecuteAsync() in C:\Code\Umbraco\Facs.Web.UI\Views\Layout.cshtml:line 26 at

AndyButland commented 1 year ago

I'm not sure here, but following through the stack trace I can see that Form's is calling into a CMS method MemberManager.IsMemberAuthorizedAsync and it's that that throws the exception.

I'd suggest just running a test to be sure this is a wider issue with CMS and your Okta setup, and isolate Forms from the issue.

For example you could add this code to one of your views, and see if you get the same exception when logged in:

@using Umbraco.Cms.Core.Security;

@inject IMemberManager _memberManager;

<div>Is authorised: @(await _memberManager.IsMemberAuthorizedAsync())</div>

I suspect you will get a similar exception thrown.

Assuming you do, it looks to be because the authenticated member's ID isn't being recognised as either a GUID or an integer, and the CMS code is expecting one or the other. See for example:

I may need to ask you to transfer this information to the CMS issue tracker, or the forums. It could be a CMS update is needed, or maybe there's something you can do with your Okta integration.

First though, I'm wondering if we can work around the issue. Once you've tried the code sample above and verified you get an exception when logged in, could you try replacing the <div> with the following please, and let me know the results you see when logged in? I'm wondering if they will call the same CMS methods and hence still throw an exception, or whether these will work OK. Which if so, we could switch to using instead.

<div>Is logged in: @(_memberManager.IsLoggedIn())</div>
@if (_memberManager.IsLoggedIn())
{
    var identityUser = await _memberManager.GetUserAsync(User);
    if (identityUser != null)
    {
        <div>Id: @identityUser.Id</div>
        <div>Key: @identityUser.Key</div>
        <div>Email: @identityUser.Email</div>
    }
}
TheK272 commented 1 year ago

Thanks for the reply. The first code snippet threw the same error, like you suspected. The second code snippet throws the same error. I'm guessing that means I should create a ticket in the Umbraco CMS repo?

This is the error message from the second code snippet that you gave me: InvalidOperationException: Unable to convert user ID (00u3j5olob8akHODo1d7)to int using InvariantCulture Umbraco.Cms.Core.Security.UmbracoUserStore<TUser, TRole>.UserIdToInt(string userId) Umbraco.Cms.Core.Security.MemberUserStore.FindUserAsync(string userId, CancellationToken cancellationToken) Umbraco.Cms.Core.Security.UmbracoUserStore<TUser, TRole>.FindByIdAsync(string userId, CancellationToken cancellationToken) Microsoft.AspNetCore.Identity.UserManager.FindByIdAsync(string userId) Microsoft.AspNetCore.Identity.UserManager.GetUserAsync(ClaimsPrincipal principal) AspNetCore.Views_Layout.ExecuteAsync() in Layout.cshtml + var identityUser = await _memberManager.GetUserAsync(User);

AndyButland commented 1 year ago

Yes, I think that's a good idea. I'll close this one as it doesn't look like there's anything in Forms itself we can do - other than perhaps a try/catch - but really that would hide the issue rather than resolve it. Please link to this issue in the CMS repo in case there's information here that's useful - or if someone sees a fix we could look at in Forms and wants to re-open it.