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

时间:2016-02-29 10:25:39

标签: asp.net-mvc authentication asp.net-identity

像slack这样的移动应用程序已经普及了允许用户获得一次性登录链接的想法(Slack称之为神奇的登录链接)。

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

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

我生成一个这样的标记:

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

此令牌已添加到用户的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);

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

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

2 个答案:

答案 0 :(得分:1)

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

您应该在令牌中添加UserIdExpirationTime。您可以将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);
    }
}

您可以创建并使用MagicLinkTokenDataFormat作为单身人士。

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

生成令牌

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

 string token = magicLinkTokenDataFormat.Protect(magicLinkToken);

验证令牌

MagicLinkToken magicLinkToken = magicLinkTokenDataFormat.Unprotect(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);
}

答案 1 :(得分:0)

您可以生成令牌并将其存储在另一个表中,因此使安全标记无效不会使登录无效。