Search code examples
asp.net-coreuniqueasp.net-core-identity

Require unique phone number in Asp.Net Core Identity


In Asp.Net Core Identity framework, I can easily require a unique email address by setting RequireUniqueEmail = true.

Is there any way to do the same for the user's phone number? Note that I don't want to require a confirmed phone number to sign in. The user is not required to enter a phone number but if they do, it must be unique.


Solution

  • You can try this, basically enforce it at the Db level first, followed by implementing proper checks at the Manager level.

    At DbContext, I declared the indexing and uniqueness of both the username and email properties.

    taken from the link

        // ================== Customizing IdentityCore Tables ================== //
    
                builder.Entity<User>().ToTable("Users").Property(p => p.Id).HasColumnName("Id").ValueGeneratedOnAdd();
                builder.Entity<User>(entity =>
                {
                    entity.HasIndex(u => u.UserName).IsUnique();
                    entity.HasIndex(u => u.NormalizedUserName).IsUnique();
                    entity.HasIndex(u => u.Email).IsUnique();
                    entity.HasIndex(u => u.NormalizedEmail).IsUnique();
    
                    entity.Property(u => u.Rating).HasDefaultValue(0).IsRequired();
                    entity.HasMany(u => u.UserRoles).WithOne(ur => ur.User)
                        .HasForeignKey(ur => ur.UserId).OnDelete(DeleteBehavior.Restrict);
                    entity.HasMany(u => u.UserClaims).WithOne(uc => uc.User)
                        .HasForeignKey(uc => uc.UserId).OnDelete(DeleteBehavior.Restrict);
                });
    

    And for the Manager level code:

        /// <summary>
                /// Sets the <paramref name="email"/> address for a <paramref name="user"/>.
                /// </summary>
                /// <param name="user">The user whose email should be set.</param>
                /// <param name="email">The email to set.</param>
                /// <returns>
                /// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/>
                /// of the operation.
                /// </returns>
                public override async Task<IdentityResult> SetEmailAsync(User user, string email)
                {
                    var dupeUser = await FindByEmailAsync(email);
        
                    if (dupeUser != null)
                    {
                        return IdentityResult.Failed(new IdentityError() {
                            Code = "DuplicateEmailException", // Wrong practice, lets set some beautiful code values in the future
                            Description = "An existing user with the new email already exists."
                        });
                    }
        
                    // Perform dupe checks
        
                    // Code that runs in SetEmailAsync
                    // Adapted from: aspnet/Identity/blob/dev/src/Core/UserManager.cs
                    //
                    // ThrowIfDisposed();
                    // var store = GetEmailStore();
                    // if (user == null)
                    // {
                    //     throw new ArgumentNullException(nameof(user));
                    // }
        
                    // await store.SetEmailAsync(user, email, CancellationToken);
                    // await store.SetEmailConfirmedAsync(user, false, CancellationToken);
                    // await UpdateSecurityStampInternal(user);
        
                    //return await UpdateUserAsync(user);
        
                    return await base.SetEmailAsync(user, email);
                }
    

    This way, we retain .NET Core Identity Code's Integrity while enforcing the uniqueness of the property/properties that we want.

    Note that the samples above are for email as of now. Simply do the same and then instead of modifying SetEmailAsync, work on SetPhoneNumberAsync at UserManager.cs.