I am trying to setup my authentication using the authorization_code
grant flow. I had it previously working with grant_type=password
, so I kind of know how the stuff is supposed to work. But when using grant_type=authorization_code
, I couldn't make it return anything other than invalid_grant
Here is my setup:
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 is the following class: https://gist.github.com/anonymous/8a0079b705423b406c00
Basically, it's just logging every step and validating it. I tried the request:
POST http://localhost:12345/auth/token
grant_type=authorization_code&code=xxxxxx&client_id=xxxxx&redirect_uri=https://xxxx.com/
Content-Type: application/x-www-form-urlencoded
It's going through:
OnMatchEndpoint
OnValidateClientAuthentication
And that's all. I expected it to call OnValidateTokenRequest
and OnGrantAuthorizationCode
next, but it just didn't. I have no idea why.
The xxxx
's in the request aren't placeholders, I tried it like that. Maybe the middleware makes some checks on its own and rejects the request because of that? I tried variants of the redirect_uri
with http
, without any protocol, without trailing slash...
It also works properly with a custom grant_type
. It so if I too desperate, I guess I can use that to simulate authorization_code
, but I'd rather not have to do that.
TL;DR
My OAuthAuthorizationServerProvider
returns {"error":"invalid_grant"}
after OnValidateClientAuthentication
when using grant_type=authorization_code
.
- Why is it stopping there?
- How can I make the whole damn thing work?
Thanks for your help!
Edit
As pointed out by RajeshKannan, I made a mistake in my configuration. I didn't provide an AuthorizationCodeProvider
instance. However, that didn't completely solve the problem, since in my case, the code is not issued by the AuthorizationCodeProvider
, and I can't just deserialize it. I anwered with the workaround I got working.
Here is what I got working. I'm not completely comfortable with that solution, but it works and should help others to fix their issues.
So, the issue is that I didn't set the AuthorizationCodeProvider
property. When a request with grant_type=authorization_code
is received, the code must be validated by that code provider. The framework assumes that the code was issued by that code provider, but that's not my case. I get it from another server and have to send the code back to it for validation.
In the standard case, where you are also the one issuing the code, the link provided by RajeshKannan describes everything you have to do.
Here is where you have to set the property:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(Paths.TokenPath),
Provider = new SampleAuthProvider(),
AuthorizationCodeProvider = new MyAuthorizationCodeProvider ()
}
And the declaration of the MyAuthorizationCodeProvider
class:
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 && 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);
}
}
}
}
Make sure that you have configured your authorization server options.
I think you should provide your authorize end point details:
AuthorizeEndpointPath = new PathString(Paths.AuthorizePath)
In the below link, the authorization code grant will be explained in detail and it lists the method which were involved in authorization code grant life cycle.
Owin Oauth authorization server
I had the same error. Things I was missing:
- Specify
OAuthAuthorizationServerOptions.AuthorizationCodeProvider
according to the documentation.
- Specify the same
client_id
as a GET-parameter when making a request to the token endpoint as you did when you received the authorization_code
.
- Override
OAuthAuthorizationServerProvider.ValidateClientAuthentication
and in this method call context.TryGetFormCredentials
. This sets the property context.ClientId
to the value from the client_id
GET-parameter. This property must be set, otherwise you'll get the invalid_grant
error. Also, call context.Validated()
.
After doing all of the above, I could finally exchange the authorization_code
to an access_token
at the token endpoint.
Thanks scenario, My code was missing the following two required values. Posted here in case others find it useful:
// 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);