Поток предоставления кода авторизации в Owin.Security.OAuth: возвращает invalid_grant

#asp.net #oauth-2.0 #owin #owin.security

#asp.net #oauth-2.0 #owin #owin.security

Вопрос:

Я пытаюсь настроить свою аутентификацию с помощью authorization_code потока предоставления. Я уже работал с этим ранее grant_type=password , так что я вроде знаю, как это должно работать. Но при использовании grant_type=authorization_code я не мог заставить его возвращать что-либо, кроме invalid_grant

Вот моя настройка:

 app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
    AllowInsecureHttp = true,
    TokenEndpointPath = new PathString("/auth/token"),
    AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
    Provider = new SampleAuthProvider()
});

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
    AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
    AuthenticationType = "Bearer"
});
  

SampleAuthProvider — это следующий класс:https://gist.github.com/anonymous/8a0079b705423b406c00

По сути, он просто регистрирует каждый шаг и проверяет его. Я попробовал запрос:

 POST http://localhost:12345/auth/token
grant_type=authorization_codeamp;code=xxxxxxamp;client_id=xxxxxamp;redirect_uri=https://xxxx.com/
Content-Type: application/x-www-form-urlencoded
  

Он проходит:

  • OnMatchEndpoint
  • OnValidateClientAuthentication

И это все. Я ожидал, что он вызовет OnValidateTokenRequest и OnGrantAuthorizationCode next, но этого просто не произошло. Я понятия не имею, почему.

xxxx В запросе не являются заполнителями, я пробовал это так. Может быть, промежуточное программное обеспечение выполняет некоторые проверки самостоятельно и отклоняет запрос из-за этого? Я пробовал варианты redirect_uri с http , без какого-либо протокола, без косой черты в конце…

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

TL; DR

Мой OAuthAuthorizationServerProvider возвращает {"error":"invalid_grant"} после OnValidateClientAuthentication при использовании grant_type=authorization_code .

  • Почему это останавливается там?
  • Как я могу заставить всю эту чертову штуку работать?

Спасибо за вашу помощь!


Редактировать

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

Ответ №1:

Вот что у меня получилось. Меня не совсем устраивает это решение, но оно работает и должно помочь другим исправить их проблемы.


Итак, проблема в том, что я не установил AuthorizationCodeProvider свойство. Когда получен запрос с grant_type=authorization_code , код должен быть проверен этим поставщиком кода. Платформа предполагает, что код был выпущен этим поставщиком кода, но это не мой случай. Я получаю его с другого сервера и должен отправить код обратно на него для проверки.

В стандартном случае, когда вы также являетесь тем, кто выдает код, ссылка, предоставленная RajeshKannan, описывает все, что вам нужно сделать.

Здесь вам нужно установить свойство:

 app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString(Paths.TokenPath),
    Provider = new SampleAuthProvider(),
    AuthorizationCodeProvider = new MyAuthorizationCodeProvider ()
}
  

И объявление MyAuthorizationCodeProvider класса:

 internal class MyAuthorizationCodeProvider : AuthenticationTokenProvider
{
    public override async Task ReceiveAsync(
        AuthenticationTokenReceiveContext context)
    {
        object form;
        // Definitely doesn't feel right
        context.OwinContext.Environment.TryGetValue(
                "Microsoft.Owin.Form#collection", out form); 
        var redirectUris = (form as FormCollection).GetValues("redirect_uri");
        var clientIds = (form as FormCollection).GetValues("client_id");
        if (redirectUris != null amp;amp; clientIds != null)
        {
            // Queries the external server to validate the token
            string username = await MySsoService.GetUserName(context.Token,
                                                             redirectUris[0]);
            if (!string.IsNullOrEmpty(username))
            {
                var identity = new ClaimsIdentity(new List<Claim>()
                {
                    // I need the username in  GrantAuthorizationCode
                    new Claim(ClaimTypes.NameIdentifier, username) 
                }, DefaultAuthenticationTypes.ExternalBearer);

                var authProps = new AuthenticationProperties();

                // Required. The request is rejected if it's not provided
                authProps.Dictionary.Add("client_id", clientIds[0]); 

                // Required, must be in the future
                authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1); 

                var ticket = new AuthenticationTicket(identity, authProps);
                context.SetTicket(ticket);
            }
        }
    }
}
  

Комментарии:

1. Привет @scenario, тебе удалось найти лучший способ? У меня такая же проблема

2. Извините, я прекратил работу над этим проектом, поэтому у меня нет дальнейших обновлений по этой теме. Однако это решение сработало хорошо. API просто разработан с учетом других вариантов использования. Возможно, в будущих обновлениях это будет выполнимо без жестко закодированных ключей, приведений и ко.

Ответ №2:

У меня была та же ошибка. Чего мне не хватало:

  • Укажите OAuthAuthorizationServerOptions.AuthorizationCodeProvider в соответствии с документацией.
  • Укажите то же самое client_id , что и GET-параметр, при отправке запроса к конечной точке токена, что и при получении authorization_code .
  • Переопределите OAuthAuthorizationServerProvider.ValidateClientAuthentication и в этом вызове метода context.TryGetFormCredentials . Это устанавливает свойство context.ClientId в значение из client_id GET-параметра. Это свойство должно быть установлено, иначе вы получите invalid_grant ошибку. Кроме того, вызов context.Validated() .

После выполнения всего вышеперечисленного я смог, наконец, обменять authorization_code на access_token в конечной точке токена.

Комментарии:

1. У меня не работает. Я делаю все это, но в то время как мои реализации для OAuthAuthorizationServerProvider.ValidateClientAuthentication и IAuthenticationTokenProvider.ReceiveAsync вызываются (в таком порядке), моя реализация для OAuthAuthorizationServerProvider.GrantAuthorizationCode никогда не запускается, и в итоге я получаю 400: invalid_grant.

2. плюс 1, хотя, поскольку предложение client_id было хорошим и действительно требовалось.

Ответ №3:

Благодаря сценарию, в моем коде отсутствовали следующие два обязательных значения. Опубликовано здесь на случай, если другие сочтут это полезным:

             // Required. The request is rejected if it's not provided
            authProps.Dictionary.Add("client_id", clientIds[0]); 

            // Required, must be in the future
            authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1); 
  

Ответ №4:

Убедитесь, что вы настроили параметры сервера авторизации. Я думаю, вам следует предоставить данные о конечной точке авторизации:

  AuthorizeEndpointPath = new PathString(Paths.AuthorizePath)
  

По ссылке ниже будет подробно объяснено предоставление кода авторизации, и в нем перечислены методы, которые были задействованы в жизненном цикле предоставления кода авторизации.

Сервер авторизации Owin Oauth

Комментарии:

1. Спасибо, ваш ответ направил меня на правильный путь. Я отредактировал вопрос, чтобы объяснить, что мне пришлось изменить. Но это просто кажется странным. Мне не хватает более простого способа получить параметры запроса и передать их поставщику? Или, может быть, я не должен связываться с моим сервером единого входа таким образом?

Ответ №5:

Ответ @dgn более или менее сработал для меня. Это просто расширение к этому. Как оказалось, вы можете предоставить любую строку, которую вы хотите, ClaimsIdentity конструктору. Следующее работает так же хорошо и удваивается как подробный комментарий к коду:

 var identity = new ClaimsIdentity(
    @"Katana - What a shitty framework/implementation.
    Unintuitive models and pipeline, pretty much have to do everything, and the docs explain nothing. 
    Like what can go in here? WTF knows but turns out as long as _something_ is in here, 
    there is a client_id key in your AuthenticationProperties with the same value as 
    what's set inside your implementation for OAuthAuthorizationServerProvider.ValidateClientAuthentication, and
    your AuthenticationProperties.ExpiresUtc is set to some time in the future, it works.
    Oh and you don't actually need to supply an implementation for OAuthAuthorizationServerProvider.GrantAuthorizationCode...
    but if you are using the resource owner grant type, you _do_ need to supply an implementation of 
    OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials. Hmm. Whatever.
    Katana and IdenetityServer - two frameworks that are absolute garbage. In the amount of time it took me to
    figure out all the observations in this paragraph, I could've written my own /token endpoint."
);
  

Комментарии:

1. Я несколько раз попадался в эту ловушку 😀 Это не имеет ничего общего с этими фреймворками, но вам нужно передать тип аутентификации конструктору, если вы хотите аутентифицированный идентификатор, это можно найти в примечаниях в документах: learn.microsoft.com/en-us/dotnet/api /…

Ответ №6:

Я решил это с помощью следующего простейшего примера и хотел бы поделиться им. Надеюсь, кто-нибудь найдет это полезным.

Кажется, что промежуточное программное обеспечение проверит, redirect_uri существует ли ключ в словаре AuthenticationProperties , удалит его, и все будет работать нормально (с проверенным контекстом).

Упрощенный пример AuthorizationCodeProvider мог бы быть таким:

 public class AuthorizationCodeProvider:AuthenticationTokenProvider {
    public override void Create(AuthenticationTokenCreateContext context) {
        context.SetToken(context.SerializeTicket());
    }

    public override void Receive(AuthenticationTokenReceiveContext context) {
        context.DeserializeTicket(context.Token);

        context.Ticket.Properties.Dictionary.Remove("redirect_uri"); // <-
    }
}
  

И не забудьте проверить контекст в переопределенном методе OAuthAuthorizationServerProvider.ValidateClientAuthentication . Опять же, вот упрощенный пример, который наследуется от ApplicationOAuthProvider класса проекта шаблона:

 public partial class DefaultOAuthProvider:ApplicationOAuthProvider {
    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) {
        if(null!=context.RedirectUri) {
            context.Validated(context.RedirectUri);
            return Task.CompletedTask;
        }

        return base.ValidateClientRedirectUri(context);
    }

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
        if(context.TryGetFormCredentials(out String clientId, out String clientSecret)) {
            // Specify the actual expected client id and secret in your case
            if(("expected-clientId"==clientId)amp;amp;("expected-clientSecret"==clientSecret)) {

                context.Validated(); // <-

                return Task.CompletedTask;
            }
        }

        return base.ValidateClientAuthentication(context);
    }

    public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
    }
}
  

Обратите внимание, что если вы вызываете context.Validated с определенным идентификатором клиента, то вам нужно будет указать тот же client_id в свойствах заявки, вы можете сделать это с помощью метода AuthenticationTokenProvider.Receive