OAuth с пользовательской проверкой подлинности JWT

Я изо всех сил пытаюсь реализовать собственный поток аутентификации с помощью OAuth и JWT. В основном это должно происходить следующим образом:

  • Пользователь нажимает при входе
  • Пользователь перенаправляется на стороннюю страницу входа OAuth
  • Вход пользователя на страницу
  • Я получаю access_token и запрашиваю информацию о пользователе
  • Я получаю информацию о пользователе и создаю свой собственный токен JWT для отправки туда и обратно

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

Что я сделал до сих пор:

Настроил службу аутентификации.

services.AddAuthentication(options => 
{
    options.DefaultChallengeScheme = "3rdPartyOAuth";
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie() // Added only because of the DefaultSignInScheme
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = // Ommited for brevity
})
.AddOAuth("3rdPartyOAuth", options =>
{
    options.ClientId = securityConfig.ClientId;
    options.ClientSecret = securityConfig.ClientSecret;
    options.CallbackPath = new PathString("/auth/oauthCallback");

    options.AuthorizationEndpoint = securityConfig.AuthorizationEndpoint;
    options.TokenEndpoint = securityConfig.TokenEndpoint;
    options.UserInformationEndpoint = securityConfig.UserInfoEndpoint;

    // Only this for testing for now
    options.ClaimActions.MapJsonKey("sub", "sub");

    options.Events = new OAuthEvents
    {
        OnCreatingTicket = async context =>
        {
            // Request for user information
            var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

            var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
            response.EnsureSuccessStatusCode();

            var user = JObject.Parse(await response.Content.ReadAsStringAsync());

            context.RunClaimActions(user);
        }
    };
});

Контроллер авторизации

    [AllowAnonymous]
    [HttpGet("login")]
    public IActionResult LoginIam(string returnUrl = "/auth/loginCallback")
    {
        return Challenge(new AuthenticationProperties() {RedirectUri = returnUrl});
    }

    [AllowAnonymous]
    [DisableRequestSizeLimit]
    [HttpGet("loginCallback")]
    public IActionResult IamCallback()
    {
        // Here is where I expect to get the user info, create my JWT and send it back to the client
        return Ok();
    }

Отказ от ответственности: этот поток OAuth сейчас внедряется. У меня есть поток для создания и использования моей собственной работы JWT и всего остального. Я не буду писать здесь, потому что моя проблема до этого.

Что я хочу

В посте Джерри видно, что он ставит DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;. При этом, когда достигается /auth/loginCallback, у меня есть претензии пользователя в HttpContext. Проблема в том, что мой DefaultAuthenticateScheme установлен на JwtBearersDefault, и когда вызывается loginCallback, я не вижу утверждений пользователя нигде в Request. Как я могу получить доступ к информации, полученной на OnCreatingTicketEvent в моем обратном вызове в этом сценарии?

Бонусный вопрос: Я мало что знаю об OAuth (конечно, теперь все ясно). Вы могли заметить, что мой options.CallbackPath отличается от RedirectUri, переданного в Challenge в конечной точке login. Я ожидал, что option.CallbackPath будет вызываться поставщиком OAuth 3-й части, но это не так (очевидно). Мне пришлось установить для CallbackPath то же значение, которое я установил в конфигурации провайдера OAuth (например, учебник Jerries с GitHub), чтобы он работал. Это правильно? Обратный вызов используется только для настройки соответствия? Я даже могу прокомментировать конечную точку CallbackPath, и она продолжит работать так же...

Спасибо!

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
0
951
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

изменить [AllowAnonymous]

в [Authorize]

на конечной точке 'loginCallback' (метод AuthController.IamCallback)

Ответ принят как подходящий

Авторизация

Как Джерри упомянул в своем посте, есть отличное объяснение промежуточного программного обеспечения аутентификации: https://digitalmccullough.com/posts/aspnetcore-auth-system-demystified.html

Вы можете увидеть блок-схему в разделе Поток аутентификации и авторизации

Второй шаг — ПО промежуточного слоя аутентификации вызывает аутентификацию обработчика по умолчанию.

Поскольку вашим обработчиком аутентификации по умолчанию является Jwt, контекст не заполняется данными пользователя после потока oauth, так как он использует CookieAuthenticationDefaults.AuthenticationScheme

Пытаться:

[AllowAnonymous]
[DisableRequestSizeLimit]
[HttpGet("loginCallback")]
public IActionResult IamCallback()
{
    //
    // Read external identity from the temporary cookie
    //
    var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    
    if (result?.Succeeded != true)
    {
        throw new Exception("Nein");
    }

    var oauthUser = result.Principal;

    ...

    return Ok();
}

Сводка отличных схем: Схемы аутентификации ASP.NET Core 2

Вы можете сохранить своего пользователя с помощью https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhttpcontextextensions.signinasync?view=aspnetcore-2.2

Бонус

I did have to set the CallbackPath to the same value I have set in the OAuth provider configuration (like Jerries tutorial with GitHub) for it to work. Is that right?"

да. По соображениям безопасности зарегистрированный uri обратного вызова (на сервере авторизации) и предоставленный uri обратного вызова (отправленный клиентом) ДОЛЖНЫ совпадать. Таким образом, вы не можете изменить его случайным образом, или, если вы его измените, вы также должны изменить его на сервере авторизации.

Если этого ограничения не было, т.е. электронное письмо со ссылкой, сформированной по электронной почте (с измененным URL-адресом обратного вызова), может получить грант. Это называется Open Redirect, на него ссылается и rfc: https://www.rfc-editor.org/rfc/rfc6749#section-10.15

У OWASP отличное описание: https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md

I can even comment the endpoint CallbackPath points to and it keep working the same way..."

Это потому, что вашему клиенту доверяют (вы предоставляете свой секрет, и вы не являетесь полноценным одностраничным приложением). Поэтому вам необязательно отправлять uri обратного вызова. Но ЕСЛИ вы отправляете его, он ДОЛЖЕН совпадать с зарегистрированным на сервере. Если вы его не отправите, сервер авторизации перенаправит на URL-адрес, зарегистрированный на его стороне.

https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1

redirect_uri НЕОБЯЗАТЕЛЬНЫЙ. Как описано в разделе 3.1.2.

https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2

Сервер авторизации перенаправляет пользовательский агент на конечная точка перенаправления клиента, ранее установленная с помощью сервер авторизации в процессе регистрации клиента или при сделать запрос на авторизацию.

https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.2

Сервер авторизации ДОЛЖЕН требовать, чтобы следующие клиенты зарегистрировали свою конечную точку перенаправления:

  • Публичные клиенты.
  • Конфиденциальные клиенты, использующие неявный тип предоставления.

Ваш клиент является конфиденциальным и использует тип предоставления кода авторизации (https://www.rfc-editor.org/rfc/rfc6749#section-1.3.1)

https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.3

Если было зарегистрировано несколько URI перенаправления, если только часть был зарегистрирован URI перенаправления, или если URI перенаправления не был зарегистрирован, клиент ДОЛЖЕН включать URI перенаправления с запрос авторизации с использованием параметра запроса "redirect_uri".

Вы зарегистрировали свой uri перенаправления, поэтому сервер авторизации не выдает ошибку.

Спасибо @LeBoucher, это сработало отлично :) Скоро прочитаю все ваши ссылки, уже дам правильный ответ, потому что срок действия награды истекает

João Menighin 09.04.2019 20:32

Другие вопросы по теме