ServiceStack - подтверждение по электронной почте - PullRequest
2 голосов
/ 07 мая 2019

Я пытаюсь реализовать подтверждение по электронной почте в ServiceStack. Я потратил 5 дней, пытаясь разобраться во всем механизме аутентификации, и должен сказать, что он очень сложный. Код не легко читается.

Мне трудно знать, например:

  • Почему IAuthProvider.OnAuthenticated называется? Иногда кажется, что это необязательно.
  • Почему IAuthSession.PopulateSession называется? Чем он отличается от IAuthProvider.PopulateSession?
  • Почему IRequest.SaveSession называется?
  • Что такое IAuthTokens? И почему они в какой-то момент объединены? Это потому, что пользователь может проходить проверку подлинности у множества разных провайдеров, и нам нужно объединить IAuthTokens? Они как претензии?
  • Session.FromToken только для JWT?
  • Чем отличаются IAuthWithRequest.PreAuthenticate и IAuthProvider.Authenticate? Что должно быть сделано по-другому в любом методе? Я понял, что PreAuthenticate вызывается при каждом запросе, а Authenticate вызывается при использовании / auth / {provider} (в данном случае / auth / email). Я что-то упустил?

С учетом этих вопросов, чтобы решить проблему, я попробовал два подхода:

  1. Реализация EmailConfirmationAuthProvider

    1. Переопределите значение по умолчанию UserAuth с помощью CustomUserAuth, добавив поле bool EmailConfirmed {get;set;}.

    2. Когда пользователь регистрируется, ему отправляется электронное письмо с секретным токеном. Этот токен хранится где-то.

    3. Когда пользователь нажимает на ссылку (https://blabla.com/auth/email?token=abcdef), EmailConfirmationAuthProvider.PreAuthenticate пометит CustomUserAuth.EmailConfirmed как истинное, если токен действителен и не истек. Токен удален, поэтому невозможно выполнить аутентификацию снова с этим токеном.

    4. Я не знаю, как добавить EmailConfirmed = true в сеанс или как добавить его при последующих входах в систему. И я не знаю, как не мешать другим механизмам аутентификации.

  2. Реализация EmailConfirmationService

    1. (те же шаги, что и 1.1 и 1.2)

    2. Когда пользователь нажимает на ссылку (https://blabla.com/email-confirmation?token=abcdef), EmailConfirmationService пометит CustomUserAuth.EmailConfirmed как true, если токен действителен и не истек. Токен удален и его невозможно использовать повторно тот же токен.

    3. Я не знаю, как можно повторно заполнить сеанс из CustomUserAuth, особенно если пользователь уже аутентифицирован с токеном JWT (если JwtAuthProvider является частью AuthFeature.AuthProviders). Я хочу найти способ добавить флаг EmailConfirmed в сеанс для всех последующих входов в систему этого пользователя. Я не нашел, как «изменить» токен JWT (добавив EmailConfirmed = true) и отправить его обратно в виде файла cookie, чтобы не было необходимости проверять, подтверждается ли электронная почта при каждом запросе.

По сути, я хочу ограничить доступ ко всем службам, отмеченным [Authenticate], для пользователей, которые подтвердили свои адреса электронной почты. Поправьте меня, если я ошибаюсь, но это должно относиться только к пользователям, которые зарегистрировались с использованием учетных данных (подтверждение по электронной почте не должно применяться, если пользователь входит с GoogleAuthProvider ..? Или это должно быть?)

Исключением из этого правила могут быть некоторые службы (отмеченные атрибутом, скажем, [AllowUnconfirmedEmail]), которые позволяют пользователю повторно отправлять электронное письмо с подтверждением на свой адрес (например, они ввели неправильный адрес электронной почты). EmailConfirmationService может позволить аутентифицированным пользователям отправлять новое подтверждение на новый адрес. После того, как это электронное письмо будет подтверждено, CustomUserAuth.Email & CustomUserAuth.PrimaryEmail будет обновлено новым адресом.

1 Ответ

2 голосов
/ 07 мая 2019

Обратитесь к диаграммам высокого уровня в Документах аутентификации ServiceStack , чтобы объяснить, как провайдеры аутентификации на основе сеансов и IAuthWithRequest Провайдеры аутентификации работают.

Многие из этих вопросов являются внутренними подробностями реализации, они вызываются, когда они нужны их различным провайдерам аутентификации. Переопределяемые пользователем события, к которым можно добавить пользовательскую логику, публикуются в События сеанса и аутентификации .

PreAuthenticate() вызываются IAuthWithRequest провайдерами аутентификации , которые аутентифицируются по запросу, например ключ API и JWT провайдерами аутентификации. Authenticate() вызывается при аутентификации через /auth провайдера.

Реализация подтверждения по электронной почте

Самый простой способ реализовать пользователей, которым требуется подтвержденная электронная почта, вероятно, это реализовать Пользовательскую проверку пользовательских сеансов что-то вроде:

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;
    }
}

Таким образом, аутентификация разрешается только в том случае, если у пользователя есть подтвержденный адрес электронной почты.

См. Расширение таблиц UserAuth , чтобы узнать, как использовать пользовательские UserAuth таблицы с дополнительными метаданными, такими как EmailConfirmed.

Тогда вашей службой электронной почты будет Служба, которая принимает случайную строку, например Guid (либо в CustomUserAuth, либо в отдельной таблице), которая указывает, какой пользователь подтвердил свою электронную почту. В качестве альтернативы ссылка в электронном письме может просто снова включить электронное письмо, и в этом случае вы можете сопоставить его с существующим электронным письмом в поле Email в таблице CustomUserAuth.

Если вы затем хотите аутентифицировать пользователя в том же запросе, вы можете разрешить Аутентифицированные запросы без пароля , настроив CredentialsAuthProvider с помощью:

new CredentialsAuthProvider {
    SkipPasswordVerificationForInProcessRequests = true,
}

Который затем позволит вам проходить аутентификацию в том же самом запросе "в процессе" только с именем пользователя:

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 для аутентификации пользователя с помощью сеанса JWT только для HTTP , хотя использование JWT не требуется.

Для этого решения это не требуется, но вы можете добавить дополнительную информацию к токену JWT с помощью , реализующей CreatePayloadFilter , и заполнить эти дополнительные метаданные в сеансе пользователя с помощью PopulateSessionFilter.

Я не знаю, как добавить EmailConfirmed = true к сеансу

Вы можете заполнить UserSession, внедрив OnAuthenticated() в свой Custom AuthUserSession.

В основном я хочу ограничить доступ ко всем службам, отмеченным [Аутентификация], для пользователей, которые подтвердили свои адреса электронной почты.

Переопределение Validate() в пользовательской сессии AuthUserSession, как указано выше, гарантирует, что только пользователи с подтвержденными электронными письмами могут проходить аутентификацию.

Исключением из этого правила могут быть некоторые услуги

Не существует понятия «частично аутентифицированный пользователь», который вы либо аутентифицировали (и у вас есть Аутентифицированный UserSession, присоединенный к Запросу или Кэшу), либо нет. Я бы разрешил Аутентификацию только в том случае, если они подтвердили свою электронную почту, и обновил свою подтвержденную электронную почту только в том случае, если они подтвердили предложенную электронную почту и хотят изменить ее на

.
...