Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • paul 11 posts 101 karma points
    Apr 07, 2023 @ 11:52
    paul
    0

    Umbraco Member Login & Registration settings (11.2.2)

    Dear all, My question is about Member Registration and Login following, the "out of the box" login macro's that come with Umbraco.

    Umbraco v11 comes with 5 code snippets:

    1) a partial to display the login status

    and four macro partials to

    2) Register

    3) Login

    4) Edit the Profile and

    5) use the QR registration.

    Works nice, almost, because:

    To register (2) you input a) Name, b) Email address and c) Password. When you Edit the Profile (4) you find you get a LoginName d) property which is initially the same as (b) Email. In SQL (table cmsMember) I find (b) Email, (d) LoginName and (c) Password, Input value (a) Name is not in this table. I have expanded the entire SQL database (yes... ) but could not find this property....

    The login(3) and registration(2) macro's use the MemberModelBuilderFactory and I'm trying to make heads&tails of what's going on there.

    Context.User?.Identity(?).Name retrieves the LoginName (d) I'd like some comments that explains how this works! #Magic #cookies?

    I have several things I would like to change, for instance Umbraco allows to change settings in the appsettings.json. This works nice for regex settings for the password, but not for email requirements. My desire was to only allow something that ends with a "point something" when you enter a valid email address. Now it is possible to enter email@address (so without the .com at the end). I know the next step is to include an email validation of some sort, first things first. Then I found the UserName is Email setting which surprised me because that was initially what confused me.

    I find (the need for) this setting very confusing. I've tried it, tested it (true, false) and could not get it to work so i probably do not understand what is going on. UserName == (d) LoginName? or (a) Name?

    Also, the setting MaxFailedAccessAttemptsBeforeLockout does not seem to work in Development, perhaps only in Production (I did not test the latter).

    Anyway, I would like to have full control over the properties. Umbraco provides more information about the MemberModelBuilderFactory that holds the logic behind what's going on. When I click "view code" I get a 404 everywhere on github so i bet i am not privileged to see it.

    I can think of work-arounds but I'd prefer a clean, clear login with logical instructions. I think I need to create a new, custom controller and hijack the route. I have tried more and read more stuff but think this post is already long enough. Any help is much appreciated. I hope you guys can help, Thank you!

  • Huw Reddick 1929 posts 6697 karma points MVP 2x c-trib
    Apr 07, 2023 @ 12:19
    Huw Reddick
    0

    I will try and explain as much as I can.

    The MemberModelBuilderFactory initialises the model, example below

    // Build a registration model with parameters
    var registerModel = memberModelBuilderFactory
        .CreateRegisterModel()
        .WithMemberTypeAlias("forumMember") //If you have a custom member type you can set it here
        .WithRedirectUrl(null) //you can set a url to redirect to after registering
        .WithCustomProperties(true) //this will allow you to get values for any custom properties you may have defined
        .UsernameIsEmail(false) //default is true
        .Build();
    

    If UsernameIsEmail is set to false then you must provide the username in your form, other wise it defaults to the email address.

    Now in your registration form you can ask for these fields from the model declared above

    1. Name //this is the Name, if not set it will also default to the email i think
    2. Username // if you have not set UsernameIsEmail to false this will automatically be set to email
    3. Email
    4. Password
    5. ConfirmPassword

    If you have any custom properties then you can display those using

    @if (registerModel.MemberProperties != null)
    {
        for (var i = 0; i < registerModel.MemberProperties.Count; i++)
        {
                <div class="mb-3">
                    <label asp-for="@registerModel.MemberProperties[i].Name" class="form-label"></label>
                    <input asp-for="@registerModel.MemberProperties[i].Value" class="form-control"/>
                    <input asp-for="@registerModel.MemberProperties[i].Alias" type="hidden"/>
                    <span asp-validation-for="@registerModel.MemberProperties[i].Value" class="form-text text-danger"></span>
                </div>
        }
    }
    

    You don't need the MemberModelBuilder for login, just the LoginModel

    @using Umbraco.Cms.Web.Common.Models
    @using Umbraco.Extensions
    @{
            var model = new LoginModel();
    }
    <div class="login-form">
    
        @using (Html.BeginUmbracoForm<YOURMemberController>(
        "HandleLogin", new { RedirectUrl = Model.RedirectUrl }))
        {
                <hr />
                <div asp-validation-summary="All" class="text-danger"></div>
            <div class="mb-3">
                <label asp-for="@model.Username" class="form-label"></label>
                <input asp-for="@model.Username" class="form-control" autocomplete="new-password" aria_required="true" />
                <span asp-validation-for="@model.Username" class="form-text text-danger"></span>
            </div>
            <div class="mb-3">
                <label asp-for="@model.Password" class="form-label"></label>
                <input asp-for="@model.Password" class="form-control" autocomplete="new-password" aria_required="true" type="password" />
                <span asp-validation-for="@model.Password" class="form-text text-danger"></span>
            </div>
            <div class="mb-3 form-check">
                <input asp-for="@model.RememberMe" class="form-check-input" aria_required="true" type="checkbox" />
                <label asp-for="@model.RememberMe" class="form-check-label">
                    @Html.DisplayNameFor(m => model.RememberMe)
                </label>
            </div>   
            <button type="submit" class="btn btn-dark">Log in</button>
    
            }
    
    </div>
    
  • Huw Reddick 1929 posts 6697 karma points MVP 2x c-trib
    Apr 07, 2023 @ 12:27
    Huw Reddick
    1

    Context.User?.Identity(?).Name retrieves the LoginName

    Ii is from the .net authentication cookie, it is using standard microft identities https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=visual-studio

    MaxFailedAccessAttemptsBeforeLockout

    I have not tested, but it should still work in dev the same

  • paul 11 posts 101 karma points
    Apr 20, 2023 @ 12:09
    paul
    0

    Thanks for the reply! Much appreciated!!

    to be more clear, there are 4 forms: registration, edit, login and the login status.

    In registration Model.Name Model.Email are declared.

    <td><input asp-for="@registerModel.Name" class="form-control" aria-required="true" /></td>        
    <td><input asp-for="@registerModel.Email" class="form-control" autocomplete="username" aria-required="true" type="email" /></td>
    

    In edit, Model.Name Model.Email and Model.UserName are available. (I wish password was there by Umbraco default). Notice UserName is new. UserName is the first value required to login (besides password).

        <td><input asp-for="@profileModel.Name" class="form-control" aria-required="true" /></td>
        <td><input asp-for="@profileModel.Email" class="form-control" autocomplete="username" aria-required="true" /></td>
    @if (!string.IsNullOrWhiteSpace(profileModel.UserName))
            {            
                <td><label asp-for="@profileModel.UserName" class="form-label"></label>Login</td>
                <td><input asp-for="@profileModel.UserName" class="form-control" autocomplete="username" aria-required="true" /></td>
                <td><span asp-validation-for="@profileModel.UserName" class="form-text text-danger"></span></td>           
            }
    

    In the login form you need to provide Model.Username and Model.Password.

    <td><input asp-for="@loginModel.Username" class="form-control"/></td>
    

    Now where did Model.Name go? In the login status I can retrieve the UserName and authentication status:

       <a href="/edit-login/"><strong style="margin: 1px 10px">@Context?.User?.Identity?.Name</strong>
            <a href="/edit-login/"><strong style="margin: 1px 10px">@Context?.User?.Identity?.IsAuthenticated</strong>
    
            @using (Html.BeginUmbracoForm<UmbLoginStatusController>("HandleLogout", new { RedirectUrl = logoutModel.RedirectUrl }))
            {
                <button type="submit" class="btn btn-primary">Log out!!</button>
            }
            </a>
    

    I find this confusing, because .Name displays the UserName which we declared in the registration model as Model.Email and not Name. Also, what other information is in such a cookie? Not email address or custom properties is presume.

    In SQL i cannot find the original value for Model.Name. I think I've checked all collumns from tables :-) The link you gave me says the information should be in dbo.AspNetUsers but that's not the case in our case. It should be easy to find in SQL right? Or is this a false assumption. In SQL I find LoginName and Email. Login Name corresponds to UserName (which we declared as email and is displayed in status as Name from the cookie) and Email to Email.

    I understand the UserName (or LoginName?) is retrieved from a cookie when logged in. Does it make sense to retrieve something like the original value of Model.Name and show that (how?) or would you not advise this.

    about registration: when i set .UsernameIsEmail(true) in the registration form as parameter for the memberModelBuilderFactory when I build the registerModel and set the value in appsettings.json

     "Umbraco": {
        "CMS": {
          "Runtime": {  
      "MaxQueryStringLength": 90,
      "MaxRequestLength": 2048
          },
          "Security": {
            "KeepUserLoggedIn": false,
            "HideDisabledUsersInBackOffice": false,
            "AllowPasswordReset": true,
            "AuthCookieName": "UMB_UCONTEXT",
            "AuthCookieDomain": "",
            "UsernameIsEmail": true,
    

    It does not seem to change much. Perhaps because the collumn in the table in sql is already there... i can still change both seperatly, they are not linked, So confusing!! Hope I can add some comments to the project some day to make this more clear! Tnx in advance

  • Keith 74 posts 240 karma points
    Apr 20, 2023 @ 13:07
    Keith
    1

    I might have some of my terminology or facts wrong here, and im still on V10 but my two cents below are based on what I have found:

    Umbraco uses .net core identity under the hood. It uses a custom implementation of the stores though, so you wont find a "dbo.AspNetUsers" table like a vanilla implementation of identity. For members, youll find "cmsMember" and for users, youll find "umbracoUser". These tables, as you have seen, store the basic values used by .net core identity.

    However, apart from the basic values used for authentication, the members are stored as nodes, in a similar but slightly different way to all other content.

    If you go into Settings > Member Types > you will see the different member types. When you create an instance of one of these, you are creating a content node to store the data, just like a page in the CMS.

    If you want to find where the member name is in the database, you need to find the node in the [umbracoNode] table.

    SELECT * FROM [dbo].[umbracoNode] where text = 'Name of User'
    

    If you need to find the values of custom properties of a member in the database, you need to look at the [umbracoPropertyData] table.

    Its probably best not to try to decypher the database though. I think all you need to use in any custom code is IMemberService for updating the member data and MemberManager for updating usernames, passwords etc...

    Its not an ideal implementation IMO. The more members you have, the larger the content node and property tables get, the slower the queries become etc.

    Updating member properties actually lock the content tables entirely too. I learned this the hard way trying to import members in parallel a rake load of them failed. Essentially, because of the locking, you can only create/update/delete one member at a time, in the same way as a piece of content in the content tree.

    I have 400,000 member records and have found the above issues too much of an issue for us, so we are moving away to a custom .net core identity implementation + identity server. But if you are expecting 100s or 1000s of members, it should be no problem at all.

Please Sign in or register to post replies

Write your reply to:

Draft