Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

One-time login link in asp.net identity

Some mobile apps like slack have popularized the idea of allowing your users to get a one-time use login link (Slack calls this the magic login link).

The idea is that you enter your email and instead of having to enter your password of a mobile, you request a magic login link that can be used once to log you in by opening that link on your phone.

I'm implementing this in asp.net identity 2.1, and I'm unsure how to ensure that the link that's generated can only be used once.

I generate a token like this:

var token = await _userManager.GenerateUserTokenAsync("MyLoginLink",user.Id);

This token is added to a URL for the user. The action method that the link redirects you to checks that the link is valid for that user, and then logs you in:

public async Task<ActionResult> LoginLink(string email, string token)
{

    var user = await _userManager.FindByNameAsync(email);

    // some checks ommited

    //check for an expired token:
    var result = await _userManager.VerifyUserTokenAsync(user.Id, "MyLoginLink", token);
    if (!result)
    {
        // Failed
        return RedirectToAction("Login");
    }

    await _userManager.UpdateSecurityStampAsync(user.Id);
    await SignInAsync(user, true);

Now - if I update the security stamp with user.UpdateSecurityStamp, that re-generates the security stamp, which will invalidate this token, and ensure it can't be used again. The problem with that is that it also invalidates any existing logins, so if the user is also logged in on a desktop, they'll be forced to logoff and on again.

Is there a relatively simple way to create one-time use token like this in asp.net identity, that doesn't invalidate all existing logins?

like image 533
Matt Roberts Avatar asked Feb 29 '16 10:02

Matt Roberts


People also ask

What is ASP.NET identity?

ASP.NET Identity is Microsoft's user management library for ASP.NET. It includes functionality such as password hashing, password validation, user storage, and claims management. It usually also comes with some basic authentication, bringing its own cookies and multi-factor authentication to the party.

What is IdentityServer4?

IdentityServer4 is a piece of software that issues security tokens to the clients. IdentityServer4 is responsible for creating a complete authentication service, with single session input and output for various types of applications, such as mobile, web, native or even other services.

What is login and registration using identity in ASP NET Core?

In this tutorial, I will create a project with Login and Registration using Identity in ASP.NET Core 3.1.ASP.NET Core Identity is an API that supports login functionality in ASP.NET Core MVC web application. Login information will be saved in identity. Identity can also be configured to use the SQL Server database.

What types of accounts does ASP NET identity support?

ASP.NET Identity also supports social accounts, which don't require the user to create a password for the app. Social accounts use a third party (such as Google, Twitter, Facebook, or Microsoft) to authenticate users. This topic covers the following:

How do I create login and logout actions in ASP NET Core?

Create Login Action » AccountController VIII. Create Login View IX. Create Logout Action » AccountController First, create your ASP.NET Core Web Application. To do that just follow the steps below. Select File > New > Project. Select ASP.NET Core Web Application. Name the project Core3.1 to have the same namespace as my project. Click OK.

How do I enable two-factor authentication in ASP NET identity?

ASP.NET Identity uses OWIN middleware for cookie-based authentication. We need to configure the OWIN cookie middleware to store a two-factor authentication cookie in the request. The cookie middleware in the application is configured during application start via the ConfigureAuth method in Startup.Auth.


1 Answers

You can generate your own token so it's not depend on ASP.Net Identity.

You should add UserId and ExpirationTime in your token. You can set ExpirationTime to one minute so a token is not valid for a long time.

If you need to make sure that your tokens are one-time only, you should store your used tokens in memory and check them in your token validation and since you have ExpirationTime you can clear your tokens soon, so it won't take so much of your memory.

public class MagicLinkToken
{
    public int UserId { get; set; }
    public DateTime ExpirationTime { get; set; }
}

public class MagicLinkTokenDataFormat : ISecureDataFormat<MagicLinkToken>
{
    private readonly IDataProtector dataProtector;

    public MagicLinkTokenDataFormat(string name, string purpose)
    {
        dataProtector = new DpapiDataProtectionProvider(name).Create(purpose);
    }

    public string Protect(MagicLinkToken data)
    {
        var json = Newtonsoft.Json.JsonConvert.SerializeObject(data);
        var bytes = System.Text.Encoding.UTF8.GetBytes(json);
        var protectedTokenBytes = dataProtector.Protect(bytes);
        return Convert.ToBase64String(protectedTokenBytes);
    }

    public MagicLinkToken Unprotect(string protectedText)
    {
        var protectedTokenBytes = Convert.FromBase64String(protectedText);
        var bytes = dataProtector.Unprotect(protectedTokenBytes);
        var json = System.Text.Encoding.UTF8.GetString(bytes);
        return Newtonsoft.Json.JsonConvert.DeserializeObject<MagicLinkToken>(json);
    }
}

You can create and use MagicLinkTokenDataFormat as singleton.

 MagicLinkTokenDataFormat magicLinkTokenDataFormat = new MagicLinkTokenDataFormat("APP_NAME", "PURPOSE");

Generate Token

 MagicLinkToken magicLinkToken = new MagicLinkToken
 {
     UserId = userId,
     ExpirationTime = DateTime.Now.AddMinutes(1)
 };

 string token = magicLinkTokenDataFormat.Protect(magicLinkToken);

Verify Token

MagicLinkToken magicLinkToken = magicLinkTokenDataFormat.Unprotect(token);
if (magicLinkToken != null && magicLinkToken.ExpirationTime < DateTime.Now && !GeneratedTokens.Contains(token))
{
    var user = await _userManager.FindByIdAsync(magicLinkToken.UserId);
    await SignInAsync(user, true);

    // add to list to ensure of one-time usage
    GeneratedTokens.Add(token);
}
like image 64
Kahbazi Avatar answered Oct 24 '22 14:10

Kahbazi