Login not possible with AspNetCore Idenity when Enable DataProtection.

Nero Rodrigues 0 Reputation points
2025-02-27T21:16:42.94+00:00

I'm configuring my application to make use of AspNetCore.Identity as well Data protection.

	public static void Main(string[] args)
    {
		var builder = WebApplication.CreateBuilder(args);
		builder.Services
			.AddIdentityApiEndpoints<IdentityUser<Guid>>(options =>
			{
		   		options.Stores.ProtectPersonalData = true;
	    	})
			.AddPersonalDataProtection<LookupProtector, LookupProtectorKeyRing>();
		builder.Services.AddDataProtection();
    	builder.Services.AddOptions<KeyManagementOptions>()
	   		.Configure<IServiceScopeFactory>((options, factory) =>
       		{
       			options.XmlRepository = new CustomXmlRepository(factory);
       		});	
		...
	}

When running the application I can register new users through the Api without a problem, however when I try to login I always get a 401 - Unauthorized. During the debugging I see that every time i send a new login request the I hit the implementation of ILookupProtector which will protect the username and will be user by user manager to retrieve the user record, however the protected value is never the same and in this case the query to get the user will return nothing.

The current documentation from AspNetCore Identity doesn't cover any information regarding the use of protected Data.

public class LookupProtector : ILookupProtector
{
    private readonly IDataProtectionProvider _dataProtectorProvider;
    public LookupProtector(IDataProtectionProvider dataProtectorProvider)
    {
        _dataProtectorProvider = dataProtectorProvider;
    }
    [return: NotNullIfNotNull("data")]
    public string? Protect(string keyId, string? data)
    {
        if (string.IsNullOrWhiteSpace(data)) return data;
        return dataProtector.CreateProtector(keyId).Protect(data);
    }
    [return: NotNullIfNotNull("data")]
    public string? Unprotect(string keyId, string? data)
    {
        if (string.IsNullOrWhiteSpace(data)) return data;
        return _dataProtectorProvider.CreateProtector(keyId).Unprotect(data);
    }
}
Developer technologies | ASP.NET | ASP.NET API
0 comments No comments
{count} votes

Answer recommended by moderator
  1. Raymond Huynh (WICLOUD CORPORATION) 3,645 Reputation points Microsoft External Staff Moderator
    2025-07-22T07:58:35.6+00:00

    Hello @Nero Rodrigues ,

    You've actually done a great job debugging and have already pinpointed the exact cause of the problem, even if it's not obvious why it's happening.

    #The Problem, Explained Simply

    Think of the Data Protection system like a high-security key-maker.

    When you register a new user, you're telling the system, "Here's the username 'Nero'. Please create a secret, encrypted version of it and store that in the database." The system does this, creating something like XyZ123@#$.

    Now, when you try to log in, you're asking the system, "Please encrypt 'Nero' again so I can find the matching user in the database."

    Here's the catch: by default, for maximum security, the key-maker is designed to create a brand new, unique key every single time. So this time, it encrypts 'Nero' into something different, like AbC987!&*.

    When the system looks in the database for AbC987!&*, it finds nothing (because the stored value is XyZ123@#$), and so it correctly tells you "Unauthorized."

    The issue isn't that your code is "wrong," but that you're using the ultra-secure, ever-changing key-maker when what you need is a consistent, predictable one for lookups.

    #The Solution: Use a "Consistent" Key-Maker

    You need to tell the Data Protection system, "For this specific purpose of looking up users, I need you to use the exact same encryption method every time."

    You do this by creating a protector with a fixed "purpose" string. This ensures the output is always the same for the same input.

    Here’s how to fix your LookupProtector class:

    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.AspNetCore.Identity;
    using System.Diagnostics.CodeAnalysis;
     
    public class LookupProtector : ILookupProtector
    {
        // This protector will be consistent because it's created once with a fixed purpose.
        private readonly IDataProtector _dataProtector;
     
        public LookupProtector(IDataProtectionProvider dataProtectorProvider)
        {
            // Create a protector with a specific, constant "purpose" string.
            // This is the key to making the encryption deterministic (predictable).
            _dataProtector = dataProtectorProvider.CreateProtector("Microsoft.AspNetCore.Identity.LookupProtector");
        }
     
        // The 'keyId' from the method argument is no longer needed because our protector's purpose is fixed.
        [return: NotNullIfNotNull("data")]
        public string? Protect(string keyId, string? data)
        {
            if (string.IsNullOrWhiteSpace(data))
            {
                return data;
            }
            return _dataProtector.Protect(data);
        }
     
        [return: NotNullIfNotNull("data")]
        public string? Unprotect(string keyId, string? data)
        {
            if (string.IsNullOrWhiteSpace(data))
            {
                return data;
            }
            return _dataProtector.Unprotect(data);
        }
    }
    

    #One Final Check: Key Storage

    Since you're using a CustomXmlRepository, make sure it's correctly saving the data protection keys to a persistent location (a shared folder, a database, Azure Blob Storage, etc.). If the application generates new keys every time it restarts, even the fix above won't work across application restarts. The keys need to be stable.

    Once you apply this change to your LookupProtector, 'Nero' will always be encrypted to the same value, your database lookups will succeed, and your login will work as expected.

    Hope this clears things up!

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Anonymous
    2025-02-28T07:34:00.23+00:00

    Hi,@Nero Rodrigues

    You may register your services follow this document:

    builder.Services .AddIdentityApiEndpoints<IdentityUser<Guid>>
    (options => 
    {
     options.Stores.ProtectPersonalData = true; 
     options.Stores.MaxLengthForKeys = 128;
    })
    services.AddScoped<ILookupProtectorKeyRing, KeyRing>();
    services.AddScoped<ILookupProtector, LookupProtector>();
    

    To have Identity encrypt your custom IdentityUser model, annotate your model fields with [ProtectedPersonalData].

    1 person found this answer helpful.
    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.