Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 580
Douglas Gandini Avatar asked Jun 24 '15 20:06

Douglas Gandini


People also ask

How authentication works in web API?

Web API assumes that authentication happens in the host. For web-hosting, the host is IIS, which uses HTTP modules for authentication. You can configure your project to use any of the authentication modules built in to IIS or ASP.NET, or write your own HTTP module to perform custom authentication.

What is AuthConfig Cs in ASP net MVC?

When you create an MVC 4 web application with the Internet Application template, the project is created with a file named AuthConfig. cs in the App_Start folder. The AuthConfig file contains code to register clients for external authentication providers.


1 Answers

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);
}
like image 54
Douglas Gandini Avatar answered Oct 18 '22 17:10

Douglas Gandini