3

I am developing an Asp.NET MVC5 App following this Google sample code.

I want the app to be authenticated and create an accesstoken (Configure stage) by a user and later on I need to be able to call Google APis' (Directory API in my case) using a refresh token (without user intervention, something like "offline").

Controller:

public async Task<ActionResult> IndexAsync(CancellationToken cancellationToken)
{
    var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
                        AuthorizeAsync(cancellationToken);

        if (result.Credential != null)
        {
          var service = new Google.Apis.Admin.Directory.directory_v1.DirectoryService(new BaseClientService.Initializer
                        {
                            HttpClientInitializer = result.Credential,
                            ApplicationName = "My Application"
                        });
                      return View();
        }
        else
        {
             return new RedirectResult(result.RedirectUri);
        }
}         

FlowMetadata implementation. (I have am using FiledataStore slightly modified (GoogleFileDataStore))

public class AppFlowMetadata : FlowMetadata
    {
        //Move to settings
        static readonly string clientId = "xxxccnvofsdfsfoj.apps.googleusercontent.com";
        static readonly string clientsecret = "xxxxxxxxxxLvtC6Qbqpp4x_";
        static readonly string datastore = "AdminSDK.Api.Auth.Store";


         private static readonly IAuthorizationCodeFlow flow =
            new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
                {
                    ClientSecrets = new ClientSecrets
                    {
                        ClientId = clientId,
                        ClientSecret = clientsecret
                    },
                    Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser, DirectoryService.Scope.AdminDirectoryUserAliasReadonly },
                    DataStore = new GoogleFileDataStore(datastore)
                });


        public override string GetUserId(Controller controller)
        {           
            return ConfigHelper.UserID.ToString();
        }

        public override IAuthorizationCodeFlow Flow
        {
            get { return flow; }
        }
    }

When I invoke the IndexAsync controller, as there is no previous accesstoken for the user, it creates a new one after the user sign in to the Google account.
This is a sample accesstoken,

{"access_token":"ya29.5gBOszGO-oYJJt4YZfF6FeaZth1f69_.....","token_type":"Bearer","expires_in":3600,"Issued":"2014-12-24T16:02:32.014+05:30"}

Question1: Why there is no Refreshtoken stored with this? How can I retrieve a refreshtoken? Question 2: If I get the refreshtoken, how should I modify the code to create a new access token and call the API (when the accesstoken is expired)? [I referred to this question , but there was no proper answer for missing refresh token.

Community
  • 1
  • 1
Dhanuka777
  • 8,331
  • 7
  • 70
  • 126

1 Answers1

9

Found the solution for Google API offline access to get the refresh token and use refresh token to create a new accesstoken,

Question1: Why there is no Refreshtoken stored with this? How can I retrieve a refreshtoken?

I have to set the access_type as offline (by default it is online) in the request. as mentioned here

I had to write my own implementation for GoogleAuthorizationCodeFlow class. Thanks to this post.

 public class ForceOfflineGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
    {
        public ForceOfflineGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }

        public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
        {
            return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
            {
                ClientId = ClientSecrets.ClientId,
                Scope = string.Join(" ", Scopes),
                RedirectUri = redirectUri,
                AccessType = "offline",
                ApprovalPrompt = "force"
            };
        }
    };

Question2: ..ow should I modify the code to create a new access token and call the API?

    //try to get results
    var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
                    AuthorizeAsync(cancellationToken);


    //// This bit checks if the token is out of date, 
    //// and refreshes the access token using the refresh token.
    if (result.Credential.Token.IsExpired(SystemClock.Default))
    {
           Google.Apis.Auth.OAuth2.Responses.TokenResponse token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse();
           //If the token is expired recreate the token
           token = await result.Credential.Flow.RefreshTokenAsync(ConfigHelper.UserID.ToString(), result.Credential.Token.RefreshToken, CancellationToken.None);

            //Get the authorization details back
            result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
     }

This worked for me! Hope this will help others....

Community
  • 1
  • 1
Dhanuka777
  • 8,331
  • 7
  • 70
  • 126
  • 1
    How is this not part of Google's documentation? Also it seems that offline access and refreshing tokens in the other libraries is trivial compared to the loops we have to jump through with the .NET library. Lost so much time looking for the solution, glad you posted it! – DavGarcia Feb 11 '16 at 18:56