AndreasElffors
AndreasElffors

Reputation: 107

ResetPassword Token How and where is it stored?

I've been trying to understand how the reset password & account confirmation works in ASP.NET Identity. I'd just like to know if the Tokens are being stored and if so, where?

The links I receive when I'm using the password reset feature look something like this

http://localhost:1470/Account/ResetPassword?userId=a8b1389c-df93-4dfc-b463-541507c1a4bc&code=yhUegXIM9SZBpPVbBtv22kg7NO7F96B8MJi9MryAadUY5XYjz8srVkS5UL8Lx%2BLPYTU6a6jhqOrzMUkkMyPbEHPY3Ul6%2B%2F0s0qQvtM%2FLLII3s29FgkcK0OnjX46Bmj9JlFCUx53rOH%2FXMacwnKDzoJ1rbrUyypZiJXloIE50Q6iPuMTUHbX9O%2B3JMZtCVXjhhsHLkTOn9IVoN6uVAOMWNQ%3D%3D

My guess is that the tokens are stored in the link itself since I cannot find any trace of it anywhere else. Maybe someone knows for sure?

Upvotes: 7

Views: 5446

Answers (2)

Ogglas
Ogglas

Reputation: 69968

@DSR is correct but I would like to add some information to this as well.

If you have set up a Web project with Individual User Accounts go to:

App_Start -> IdentityConfig.cs

There you will see code like this:

var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}

The description for DataProtectorTokenProvider<TUser, TKey> gives the information:

Represents a token provider that uses an IDataProtector to generate encrypted tokens based off of the security stamp.

https://learn.microsoft.com/en-us/previous-versions/aspnet/dn613280(v%3dvs.108)

We can however try to dig a bit deeper how it really works. The token verification will fail if different Application Pool Identities are used for creating and validating a token on a single server. This points to that the actual protection mechanism would look something like this:

System.Security.Cryptography.ProtectedData.Protect(userData, entropy, DataProtectionScope.CurrentUser);

Given that it works if all sites use the same Application Pool Identity points to this as well. Could also be DataProtectionProvider with protectionDescriptor "LOCAL=user". It should have worked with different Application Pool Identities if LOCAL=machine was set.

new DataProtectionProvider("LOCAL=user")

https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotector?view=netframework-4.7.2

https://learn.microsoft.com/en-us/uwp/api/windows.security.cryptography.dataprotection.dataprotectionprovider

dataProtectionProvider is of type IDataProtectionProvider.

It is injected in Startup.Auth.cs like this:

app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

CreatePerOwinContext is located in the assembly Microsoft.AspNet.Identity.Owin -> AppBuilderExtensions.cs. Both ASP.NET Identity and ASP.NET Core Identity are open source and can be viewed at GitHub.

public static IAppBuilder CreatePerOwinContext<T>(this IAppBuilder app,
    Func<IdentityFactoryOptions<T>, IOwinContext, T> createCallback,
    Action<IdentityFactoryOptions<T>, T> disposeCallback) where T : class, IDisposable
{
    if (app == null)
    {
        throw new ArgumentNullException("app");
    }
    if (createCallback == null)
    {
        throw new ArgumentNullException("createCallback");
    }
    if (disposeCallback == null)
    {
        throw new ArgumentNullException("disposeCallback");
    }

    app.Use(typeof (IdentityFactoryMiddleware<T, IdentityFactoryOptions<T>>),
        new IdentityFactoryOptions<T>
        {
            DataProtectionProvider = app.GetDataProtectionProvider(),
            Provider = new IdentityFactoryProvider<T>
            {
                OnCreate = createCallback,
                OnDispose = disposeCallback
            }
        });
    return app;
}

https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Owin/Extensions/AppBuilderExtensions.cs

https://archive.codeplex.com/?p=aspnetidentity#src/Microsoft.AspNet.Identity.Owin/Extensions/AppBuilderExtensions.cs

app.GetDataProtectionProvider() is in turn located in assembly Microsoft.Owin.Security that is also Open Source.

public static IDataProtectionProvider GetDataProtectionProvider(this IAppBuilder app)
{
    if (app == null)
    {
        throw new ArgumentNullException("app");
    }
    object value;
    if (app.Properties.TryGetValue("security.DataProtectionProvider", out value))
    {
        var del = value as DataProtectionProviderDelegate;
        if (del != null)
        {
            return new CallDataProtectionProvider(del);
        }
    }
    return null;
}

https://github.com/aspnet/AspNetKatana/blob/release/src/Microsoft.Owin.Security/DataProtection/AppBuilderExtensions.cs

We can also see that CreateDataProtector has a fallback to the implementation DpapiDataProtectionProvider.

private static IDataProtectionProvider FallbackDataProtectionProvider(IAppBuilder app)
{
    return new DpapiDataProtectionProvider(GetAppName(app));
}

When reading about DpapiDataProtectionProvider(DPAPI stands for Data Protection Application Programming Interface) the description says:

Used to provide the data protection services that are derived from the Data Protection API. It is the best choice of data protection when you application is not hosted by ASP.NET and all processes are running as the same domain identity.

The Create method purposes are described as:

Additional entropy used to ensure protected data may only be unprotected for the correct purposes.

The protector class itself then looks like this:

using System.Security.Cryptography;

namespace Microsoft.Owin.Security.DataProtection
{
    internal class DpapiDataProtector : IDataProtector
    {
        private readonly System.Security.Cryptography.DpapiDataProtector _protector;

        public DpapiDataProtector(string appName, string[] purposes)
        {
            _protector = new System.Security.Cryptography.DpapiDataProtector(appName, "Microsoft.Owin.Security.IDataProtector", purposes)
            {
                Scope = DataProtectionScope.CurrentUser
            };
        }

        public byte[] Protect(byte[] userData)
        {
            return _protector.Protect(userData);
        }

        public byte[] Unprotect(byte[] protectedData)
        {
            return _protector.Unprotect(protectedData);
        }
    }
}

https://learn.microsoft.com/en-us/previous-versions/aspnet/dn253784(v%3dvs.113)

Upvotes: 9

DSR
DSR

Reputation: 4668

As I mentioned in the comment

"Tokens are generated using the SecurityStamp and validating against the SecurityStamp and not storing anywhere in database or local file storage. If you update the SecurityStamp, then previous tokens are no longer valid."

Upvotes: 11

Related Questions