I've set up the following in Startup.Auth.cs:
public partial class Startup { public void ConfigureAuth(IAppBuilder app) { // Enable the Application Sign In Cookie. app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "Application", AuthenticationMode = AuthenticationMode.Active, LoginPath = new PathString("/Login"), LogoutPath = new PathString("/Logout"), }); // Enable the External Sign In Cookie. app.SetDefaultSignInAsAuthenticationType("External"); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "External", AuthenticationMode = AuthenticationMode.Passive, CookieName = CookieAuthenticationDefaults.CookiePrefix + "External", ExpireTimeSpan = TimeSpan.FromMinutes(5), }); // Enable Google authentication. app.UseGoogleAuthentication(); // Setup Authorization Server app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions { AuthorizeEndpointPath = new PathString("/OAuth/Authorize"), TokenEndpointPath = new PathString("/Token"), ApplicationCanDisplayErrors = true, #if DEBUG AllowInsecureHttp = true, #endif Provider = new SimpleOAuthProvider(), // Not Shown For Brevity AuthorizationCodeProvider = new SimpleAuthenticationTokenProvider(), RefreshTokenProvider = new SimpleAuthenticationTokenProvider() }); }
I'm not going to show the class SimpleOAuthProvider because I don't think I have a problem with it. I have a problem with SimpleAuthenticationTokenProvider:
public class SimpleAuthenticationTokenProvider : AuthenticationTokenProvider { public SimpleAuthenticationTokenProvider() : base() { this.OnCreate = CreateCode; this.OnReceive = ReceiveCode; } private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal); public void CreateCode(AuthenticationTokenCreateContext context) { context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n")); _authenticationCodes[context.Token] = context.SerializeTicket(); } public void ReceiveCode(AuthenticationTokenReceiveContext context) { string value; // I'm supposed to remove the entry for context.Token, but that's not // where my problem is. _authenticationCodes.TryGetValue(context.Token, out value); context.DeserializeTicket(value); } }
The authorization code flow works upto the point where it redirects back to the client with an authorization code (which is exactly the same value as context.Token), but returns a 400 when the client tries to exchange that authorization code for a token.
In particular, the ReceiveCode function seems to be working right, and it is called when we call the token endpoint to exchange the authorization code for an access token. It does find the entry in the concurrent dictionary for context.Token, and I assume context.DeserializeTicket works:
Before context.DeserializeTicket(value) is called, context.Ticket is null, and after it is called, context.Ticket is set, and its Identity contains all the expected claims (the ones that were set at sign in). However, after this function ReceiveCode() executes, I get a 400 (Bad Request) with the message { "error" : "invalid_grant" }. (I assume that I got to this point because client validation and redirect url validation were successful.)
My exchange token request looks like this:
POST http://authserver/token Content-Type: x-www-form-urlencoded code=whateverItGaveMyClient&state=whateverItGaveMyClient&grant_type=authorization_code&redirect_uri=http://myclient/
Something tells me that I should be overriding a function somewhere that the framework calls after ReceiveCode( ) executes. I mean, at the end of ReceiveCode( ), context.Ticket looks alright, and its Identity's .IsAuthorized IS true. So, I would assume that an access token and refresh token would be generated and given back to the client. Why am I getting a 400 that is an invalid grant error?