How can I use ASP.NET MVC Owin AccessToken in Google.Apis call?

3.5k views Asked by At

I'm trying to use the AccessToken provided by Owin in Google.Apis requests but I'm receiveing the exception System.InvalidOperationException (Additional information: The access token has expired but we can't refresh it).

My configuration of Google Authentication is OK and I can successfully login into my application with it. I store the context.AccessToken as a Claim in the authentication callback (OnAuthenticated "event" of GoogleOAuth2AuthenticationProvider).

My Startup.Auth.cs configuration (app.UseGoogleAuthentication(ConfigureGooglePlus()))

private GoogleOAuth2AuthenticationOptions ConfigureGooglePlus()
{
var goolePlusOptions = new GoogleOAuth2AuthenticationOptions()
{
    ClientId = "Xxxxxxx.apps.googleusercontent.com",
    ClientSecret = "YYYYYYzzzzzz",
    Provider = new GoogleOAuth2AuthenticationProvider()
    {
        OnAuthenticated = context =>
        {
            context.Identity.AddClaim(new System.Security.Claims.Claim("Google_AccessToken", context.AccessToken));
            return Task.FromResult(0);
        }
    },
    SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
};

goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/plus.login");
goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/userinfo.email");

return goolePlusOptions;

}

The code in which the exception is throwed (Execute() method)

var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

var accessTokenClaim = externalIdentity.FindAll(loginProvider + "_AccessToken").First();

var secrets = new ClientSecrets()
{
    ClientId = "Xxxxxxx.apps.googleusercontent.com",
    ClientSecret = "YYYYYYzzzzzz"
};

IAuthorizationCodeFlow flow =
    new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = secrets,
        Scopes = new[] { PlusService.Scope.PlusLogin, PlusService.Scope.UserinfoEmail }
    });

UserCredential credential = new UserCredential(flow, "me", new TokenResponse() { AccessToken = accessTokenClaim.Value });

var ps = new PlusService(
    new BaseClientService.Initializer()
    {
        ApplicationName = "My App Name",
        HttpClientInitializer = credential
    });

var k = ps.People.List("me", PeopleResource.ListRequest.CollectionEnum.Visible).Execute();

Is there another way to get the original AccessToken or refresh it without pass thru the entire authentication process (the user is already authenticated)?

I need to query some GooglePlus profile data such as GivenName, familyName, gender, profile picture and profile url.

1

There are 1 answers

0
Douglas Gandini On BEST ANSWER

Linda helped me with an URL pointing to a new asp.net mvc sample (https://codereview.appspot.com/194980043/).

I just had to add AccessType = "offline" to GoogleOAuth2AuthenticationOptions and save some extra info to create a new instance of TokenResponse when I need.

Google Authentication Options

private GoogleOAuth2AuthenticationOptions ConfigureGooglePlus()
{

    var goolePlusOptions = new GoogleOAuth2AuthenticationOptions()
    {
        AccessType = "offline",
        ClientId = "Xxxxxxx.apps.googleusercontent.com",
        ClientSecret = "Yyyyyyyyyy",
        Provider = new GoogleOAuth2AuthenticationProvider()
        {
            OnAuthenticated = context =>
            {
                context.Identity.AddClaim(new System.Security.Claims.Claim("Google_AccessToken", context.AccessToken));

                if (context.RefreshToken != null)
                {
                    context.Identity.AddClaim(new Claim("GoogleRefreshToken", context.RefreshToken));
                }
                context.Identity.AddClaim(new Claim("GoogleUserId", context.Id));
                context.Identity.AddClaim(new Claim("GoogleTokenIssuedAt", DateTime.Now.ToBinary().ToString()));
                var expiresInSec = (long)(context.ExpiresIn.Value.TotalSeconds);
                context.Identity.AddClaim(new Claim("GoogleTokenExpiresIn", expiresInSec.ToString()));


                return Task.FromResult(0);
            }
        },

        SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
    };

    goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/plus.login");
    goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/plus.me");
    goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/userinfo.email");

    return goolePlusOptions;
}

How to retrieve the credentials (using token info "stored" as claim)

private async Task<UserCredential> GetCredentialForApiAsync()
{
    var initializer = new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = new ClientSecrets
        {
            ClientId = "Xxxxxxxxx.apps.googleusercontent.com",
            ClientSecret = "YYyyyyyyyyy",
        },
        Scopes = new[] { 
        "https://www.googleapis.com/auth/plus.login", 
        "https://www.googleapis.com/auth/plus.me", 
        "https://www.googleapis.com/auth/userinfo.email" }
    };
    var flow = new GoogleAuthorizationCodeFlow(initializer);

    var identity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ApplicationCookie);

    var userId = identity.FindFirstValue("GoogleUserId");

    var token = new TokenResponse()
    {
        AccessToken = identity.FindFirstValue("Google_AccessToken"),
        RefreshToken = identity.FindFirstValue("GoogleRefreshToken"),
        Issued = DateTime.FromBinary(long.Parse(identity.FindFirstValue("GoogleTokenIssuedAt"))),
        ExpiresInSeconds = long.Parse(identity.FindFirstValue("GoogleTokenExpiresIn")),
    };

    return new UserCredential(flow, userId, token);
}