asp.net身份中的一次性登录链接

Mat*_*rts 12 authentication asp.net-mvc asp.net-identity

像slack这样的移动应用程序已经普及了允许用户获得一次性登录链接的想法(Slack将其称为魔术登录链接).

您的想法是输入电子邮件,而不必输入移动密码,您需要一个魔术登录链接,可以通过打开手机上的链接一次登录.

我在asp.net identity 2.1中实现这个,我不确定如何确保生成的链接只能使用一次.

我生成一个这样的标记:

var token = await _userManager.GenerateUserTokenAsync("MyLoginLink",user.Id);
Run Code Online (Sandbox Code Playgroud)

此令牌将添加到用户的URL中.链接重定向到的操作方法检查链接是否对该用户有效,然后将您登录到:

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);
Run Code Online (Sandbox Code Playgroud)

现在 - 如果我更新安全标记user.UpdateSecurityStamp,重新生成安全标记,这将使此标记无效,并确保它不能再次使用.问题在于它还会使任何现有登录失效,因此如果用户也登录到桌面,则会强制他们再次注销.

是否有一种相对简单的方法在asp.net身份中创建这样的一次性使用令牌,这不会使所有现有登录失效?

Kah*_*azi 3

您可以生成自己的令牌,因此它不依赖于 ASP.Net Identity。

您应该在令牌中添加UserId和。ExpirationTime您可以设置ExpirationTime为一分钟,这样令牌就不会长时间有效。

如果您需要确保您的令牌只是一次性的,您应该将使用过的令牌存储在内存中,并在令牌验证中检查它们,因为您可以ExpirationTime很快清除您的令牌,所以不会花费太多时间你的记忆。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以作为单例创建和使用MagicLinkTokenDataFormat

 MagicLinkTokenDataFormat magicLinkTokenDataFormat = new MagicLinkTokenDataFormat("APP_NAME", "PURPOSE");
Run Code Online (Sandbox Code Playgroud)

生成令牌

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

 string token = magicLinkTokenDataFormat.Protect(magicLinkToken);
Run Code Online (Sandbox Code Playgroud)

验证令牌

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);
}
Run Code Online (Sandbox Code Playgroud)