Search code examples
authenticationasp.net-core-mvcgoogle-oauth

Google authentication with MVC6


Following the instructions in the documentation I set up Facebook authentication like so:

  "dependencies": {
    //blah blah snip
    "Microsoft.AspNetCore.Authentication.Facebook": "1.0.0-rc2-final"
  },

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  //blah blah snip

  app.UseIdentity();

  // Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
  app.UseFacebookAuthentication(new FacebookOptions()
  {
    AppId = Configuration["Authentication:Facebook:AppId"],
    AppSecret = Configuration["Authentication:Facebook:AppSecret"],

  });

  app.UseMvc(routes =>
  {
    routes.MapRoute(
              name: "default",
              template: "{controller=Home}/{action=Index}/{id?}");
  });
}

After procuring AppId and AppSecret from Facebook and storing them using SecretManager, I fired it up, whereupon there was a successful Facebook login and much rejoicing.

Emboldened by success, I tried to set up Google authentication along the same lines.

  "dependencies": {
    //blah blah snip
    "Microsoft.AspNetCore.Authentication.Facebook": "1.0.0-rc2-final",
    "Microsoft.AspNetCore.Authentication.Google": "1.0.0-rc2-final"
  },

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  //blah blah snip

  app.UseIdentity();

  // Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
  app.UseFacebookAuthentication(new FacebookOptions()
  {
    AppId = Configuration["Authentication:Facebook:AppId"],
    AppSecret = Configuration["Authentication:Facebook:AppSecret"],

  });

  app.UseGoogleAuthentication(new GoogleOptions()
  {
    ClientId = Configuration["Authentication:Google:ClientId"],
    ClientSecret = Configuration["Authentication:Google:ClientSecret"],
  });

  app.UseMvc(routes =>
  {
    routes.MapRoute(
              name: "default",
              template: "{controller=Home}/{action=Index}/{id?}");
  });
}

It almost works but The redirect URI in the request, https://localhost:44391/signin-google, does not match the ones authorized for the OAuth client.

A little research revealed that the redirect URL is supplied by the value of the CallbackPath property of the GoogleOptions object, and it defaults to "signin-google". I don't have a signin-google path and CallbackPath = "/" produces an unhandled remote failure (whatever that is) during app initialisation so I removed it and updated the app details with Google to allow https://localhost:44391/signin-google as a redirect path.

This got me prompted to grant the app "Have offline access", so I did.

But now I get 403 Forbidden with a URL like this https://localhost:44391/signin-google?state=CfDJ8MbSjmyKVXJDoa23x_EQBp75rji1GdkCSc5HU6PFA1Ta534Ag-bZ3EtTcDpLaD5otpLXXQnqmPHRaUFbD2izzBoY_HM9vjJnVYcJNtRmkALdGQvMkcBc9J56UsJU9-uyYIGbsMMfgWhdkVry7Ssc76hJMwvjtDSFohWoqcGa7CxkFfrwfailX5QDmdfdFEtjy-txpt2c064lBpsjHUq4HeU9qIlMwq4T8yDtEsklATbkjlrVu-UvLMuTadraxVdd1s1m3NPe0LWFSkfukdhNoeEyBmkEgu3JBX4sevP3Yc29_zfojOglVxDFHTCtqDB-Tg&code=4/lFOCSBncHEH0RArYZrJ7Bd2p1URIzWxLTvJikXy1S1U&authuser=0&hd=assaohs.com.au&session_state=8b968ac03c755e48f2c1479fea9c02b9ccaa292a..4748&prompt=none#

I tried setting scope like this

  var go = new GoogleOptions()
  {
    ClientId = Configuration["Authentication:Google:ClientId"],
    ClientSecret = Configuration["Authentication:Google:ClientSecret"],

  };
  go.Scope.Add("email");
  go.Scope.Add("profile");
  app.UseGoogleAuthentication(go);

Interestingly the default scope is "openid". Adding "email" and "profile" scopes didn't produce any difference in the grant request. Adding invalid scopes produced an error, implying that these are valid recognised scopes.

Using the Google developer console to enable the Google+ API for the app resolves the 403 Forbidden error, but for reasons that escape me we are still prompted to grant permission for offline access.

The GoogleOptions object has a property AccessType which defaults to null. Setting it to "online" does not seem to make any different.

Questions

I am prompted to grant offline access. What makes the server think I want offline access? What needs to be set to prevent this? Setting AccessType = "online" in the GoogleOptions object initialiser

  var go = new GoogleOptions()
  {
    AccessType = "online",
    ClientId = Configuration["Authentication:Google:ClientId"],
    ClientSecret = Configuration["Authentication:Google:ClientSecret"],        
  };

doesn't seem to have any effect. Is this the right value?


Solution

  • Offline access is the default message for a bunch of the different scopes email and profile are two of them. There is no way to change the message short of stopping to request profile and email scopes.

    AccessType (online,Offline) isn't really what you think. Offline access will return a refresh token so you can access their data at a later time. Online access means that you are only going to be accessing their data when they are there and you don't need a refresh token.