ServiceStack - 电子邮件确认

ServiceStack - Email Confirmation

我正在尝试在 ServiceStack 中实现电子邮件确认。我花了 5 天时间试图理解所有的身份验证机制,我必须说它非常复杂。代码不易阅读。

我很难知道例如:

抛开这些问题,为了解决问题,我尝试了两种方法:

  1. 实施 EmailConfirmationAuthProvider

    1. 通过添加 bool EmailConfirmed {get;set;} 字段将默认 UserAuth 替换为 CustomUserAuth

    2. 当用户注册时,会向用户发送一封带有秘密令牌的电子邮件。此令牌存储在某处。

    3. 当用户点击 link (https://blabla.com/auth/email?token=abcdef) 时,如果令牌有效,EmailConfirmationAuthProvider.PreAuthenticate 会将 CustomUserAuth.EmailConfirmed 标记为 true并且没有过期。令牌已删除,因此无法使用该令牌再次进行身份验证。

    4. 我不知道如何将 EmailConfirmed = true 添加到会话中,也不知道如何在后续登录时添加它。而且我不知道如何不妨碍其他身份验证机制。

  2. 实施 EmailConfirmationService

    1. (步骤同1.1和1.2)

    2. 当用户点击 link (https://blabla.com/email-confirmation?token=abcdef) 时,如果令牌有效,EmailConfirmationService 会将 CustomUserAuth.EmailConfirmed 标记为 true并且没有过期。令牌已删除,无法重复使用相同的令牌。

    3. 我不知道如何从 CustomUserAuth 重新填充会话,特别是如果用户已经使用 JWT 令牌进行身份验证(如果 JwtAuthProviderAuthFeature.AuthProviders 的一部分)。我想找到一种方法来为该用户的所有后续登录向会话添加 EmailConfirmed 标志。我没有找到如何 "modify" JWT 令牌(通过添加 EmailConfirmed = true),并将其作为 cookie 发回,这样就不必在每次请求时都检查电子邮件是否得到确认。

基本上,我想将所有标有 [Authenticate] 的服务的访问权限限制为已确认其电子邮件地址的用户。如果我错了,请纠正我,但这应该只适用于使用凭据注册的用户(如果用户使用 GoogleAuthProvider 登录,则电子邮件确认不应适用?或者应该吗?)

此规则的例外情况是某些服务(标有 [AllowUnconfirmedEmail] 属性)允许用户向其地址重新发送确认电子邮件(假设他们输入了错误的电子邮件地址) ). EmailConfirmationService 可以允许经过身份验证的用户向新地址发送新确认。单击该确认电子邮件后,CustomUserAuth.Email & CustomUserAuth.PrimaryEmail 将更新为新地址。

请参考ServiceStack Authentication Docs to explain how session based Auth Providers and IAuthWithRequest Auth Providers作品中的high-level图。

其中很多问题都是内部实现细节,当不同的 Auth Provider 需要时会调用它们。您可以添加自定义逻辑的用户可覆盖事件发布在 Session and Auth Events.

PreAuthenticate()IAuthWithRequest Auth Providers that authenticate per-request like API Key and JWT 授权提供商调用。 Authenticate() 在通过 /auth 提供商进行身份验证时被调用。

正在实施电子邮件确认

实现用户需要确认电子邮件的最简单方法可能是实现 Custom UserSession Validation 类似的东西:

public class CustomUserSession : AuthUserSession
{
    public override IHttpResult Validate(IServiceBase authService, IAuthSession session, 
        IAuthTokens tokens, Dictionary<string, string> authInfo)
    {
        using (var db = HostContext.AppHost.GetDbConnection(authService.Request))
        {
            var userAuthId = int.Parse(session.UserAuthId);
            if (!db.Exists<CustomUserAuth>(x => x.Id == userAuthId && x.EmailConfirmed))
                return HttpError.Conflict($"Email not confirmed") as IHttpResult;
        }

        return null;
    }
}

所以只有当用户有一个确认的电子邮件地址时它才允许身份验证。

请参阅 Extending UserAuth Tables 了解如何将自定义 UserAuth table 与 EmailConfirmed.

等其他元数据一起使用

您的电子邮件服务将是一个接受随机字符串的服务,例如 Guid(在 CustomUserAuth 或单独的 table 中),引用哪个用户已确认他们的电子邮件.或者,电子邮件中的 link 可以再次包含该电子邮件,在这种情况下,您可以将其与 CustomUserAuth table.[=] 中 Email 字段中的现有电子邮件进行匹配40=]

如果您随后想在同一个请求中对用户进行身份验证,您可以通过配置 CredentialsAuthProvider 来允许 password-less Authenticated requests

new CredentialsAuthProvider {
    SkipPasswordVerificationForInProcessRequests = true,
}

这将让您在同一 "in process" 请求中仅使用用户名进行身份验证:

using (var service = base.ResolveService<AuthenticateService>()) //In Process
{
    return service.Post(new Authenticate {
        provider = AuthenticateService.CredentialsProvider,
        UserName = request.UserName,
        UseTokenCookie = true, // if using JWT
    });
}

您可以使用 UseTokenCookie to authenticate the User with a HTTP Only JWT Session,但不需要使用 JWT。

此解决方案不需要它,但您可以通过 implementing CreatePayloadFilter 向 JWT 令牌添加额外信息,并通过实施 PopulateSessionFilter.[=40= 在用户会话中填充额外元数据]

I don't know how to add the EmailConfirmed = true to the session

您可以通过在自定义 AuthUserSession 上实施 OnAuthenticated() 来填充 UserSession。

Basically, I want to restrict access to all services marked with [Authenticate] to users that have confirmed their email addresses.

如上所述在您的自定义 AuthUserSession 中覆盖 Validate() 将确保只有拥有已确认电子邮件的用户才能进行身份验证。

Exception to this rule would be for some services

没有 "partially authenticated user" 的概念,您要么已通过身份验证(并且已将经过身份验证的 UserSession 附加到请求或缓存),要么未通过身份验证。如果他们确认了他们的电子邮件,我只会允许身份验证,并且只有在他们已经确认他们想要将其更改为建议的电子邮件时,我才会更新他们确认的电子邮件。