2

First of all I would like to say that there is a chance that this has already been answered some place else, but please read my question carefully before coming to that conclusion, because I have seen so many samples now that do things in a different way or is in another language using components I can't easily interpret into something I know.

What I'm trying to accomplish is to make an ASP.NET Web API, that can authorize with Google aka get an access_token for further communication with Google REST APIs. That is all I want it to be able to do - the later comminucation with the Google APIs is out of the scope of my Web API - it's just the access_token (either through user consent or by refresh token if user has already previously consented).

I have tried using several different approaches including using the Google API Client Library for .NET. I'm not going to post code at this point since I'm pretty much confused by now as to which approach should be used in this scenario.

My latest read was this: https://developers.google.com/identity/protocols/OAuth2WebServer and I really wish they had added some C# samples here and not only PHP, Python and Ruby.

For what I'm trying to do, I'm not sure which type of credentials I should be using Oauth or Service Account and if OAuth should it then be for application type Web Application or Other?

It seems to make a great difference which I pick, but I haven't been able to find a guide that explains this so that there isn't any doubt which to use. What I do know though is, that some of the things I have tried worked from my local computer, but didn't as soon as it was published to an IIS.

So if you can explain to me which of the many approaches is the right one for my scenario, it would be much appreciated - any code samples are also much welcome.

Summing Up:

I want to make an ASP.NET Web API, that can get an access token from Google (an access token to communicate with Google APIs) nothing else. It has to me able to get an access token via the refresh token, so that the owner of the Google account, only has to grant access once.

Any further communication with Google APIs will happen through normal REST calls and is outside scope of my Web API.

1 Answer 1

3

I had the same problem and went down most of the same roads you went down, but having some experience writing OAuth implementations it wasn't too terribly difficult. Maybe you can use what I did, which seems to work for me. You will need to install RestSharp or use some other HttpClient.

First I wrote a GoogleAuthService that handles a few basic issues. Gets the authorization url, exchanges an authorization_code for an access_token and will refresh an access_token with a refresh_token.

GoogleAuthService

    public class GoogleAuthService : IGoogleAuthService
    {
        /// <summary>
        /// Step 1 in authorization process
        /// </summary>
        /// <param name="appId"></param>
        /// <returns></returns>
        public dynamic AuthorizationUrl(string appId)
        {
            var qs = HttpUtility.ParseQueryString("");
            qs.Add("client_id", CloudConfigurationManager.GetSetting("ga:clientId"));
            qs.Add("redirect_uri", CloudConfigurationManager.GetSetting("ga:redirectUri"));
            qs.Add("scope", CloudConfigurationManager.GetSetting("ga:scopes"));
            qs.Add("access_type", "offline");
            qs.Add("state", $"appid={appId}");
            qs.Add("response_type", "code");
            return new { Url = $"{CloudConfigurationManager.GetSetting("ga:authUrl")}?{qs.ToString()}" };
        }

        /// <summary>
        /// Take the code that came back from Google and exchange it for an access_token
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public async Task<GoogleAccessTokenResponse> AccessToken(string code)
        {
            var client = new RestClient(CloudConfigurationManager.GetSetting("ga:tokenUrl"));
            var request = new RestRequest();

            request.AddParameter("code", code, ParameterType.GetOrPost);
            request.AddParameter("client_id", CloudConfigurationManager.GetSetting("ga:clientId"), ParameterType.GetOrPost);
            request.AddParameter("client_secret", CloudConfigurationManager.GetSetting("ga:clientSecret"), ParameterType.GetOrPost);
            request.AddParameter("redirect_uri", CloudConfigurationManager.GetSetting("ga:redirectUri"), ParameterType.GetOrPost);
            request.AddParameter("grant_type", "authorization_code", ParameterType.GetOrPost);

            var response = await client.ExecuteTaskAsync<GoogleAccessTokenResponse>(request, Method.POST);
            return response.Data;
        }

        /// <summary>
        /// Take an offline refresh_token and get a new acceses_token
        /// </summary>
        /// <param name="refreshToken"></param>
        /// <returns></returns>
        public async Task<GoogleRefreshTokenResponse> RefreshToken(string refreshToken)
        {
            var client = new RestClient(CloudConfigurationManager.GetSetting("ga:tokenUrl"));
            var request = new RestRequest();

            request.AddParameter("refresh_token", refreshToken, ParameterType.GetOrPost);
            request.AddParameter("client_id", CloudConfigurationManager.GetSetting("ga:clientId"), ParameterType.GetOrPost);
            request.AddParameter("client_secret", CloudConfigurationManager.GetSetting("ga:clientSecret"), ParameterType.GetOrPost);
            request.AddParameter("grant_type", "refresh_token", ParameterType.GetOrPost);

            var response = await client.ExecuteTaskAsync<GoogleRefreshTokenResponse>(request, Method.POST);
            return response.Data;
        }
    }

GoogleAccessTokenResponse

public class GoogleAccessTokenResponse
{
    /// <summary>
    /// Initial token used to gain access
    /// </summary>
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
    /// <summary>
    /// Use to get new token
    /// </summary>
    [JsonProperty("refresh_token")]
    public string RefreshToken { get; set; }
    /// <summary>
    /// Measured in seconds
    /// </summary>
    [JsonProperty("expires_in")]
    public int ExpiresIn { get; set; }
    /// <summary>
    /// Should always be "Bearer"
    /// </summary>
    [JsonProperty("token_type")]
    public string TokenType { get; set; }
}

GoogleRefreshTokenResponse

public class GoogleRefreshTokenResponse
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
    [JsonProperty("expires_in")]
    public int ExpiresIn { get; set; }
    [JsonProperty("token_type")]
    public string TokenType { get; set; }
}

Lastly you will need a Callback handler to accept the authorization_code.

GoogleOAuthController

public class GoogleOAuthController : Controller
{
    private readonly ITenantGoogleAuthenticationService service;
    private readonly ITenantService tenantService;
    private readonly IGoogleAuthService googleAuthService; 
    public GoogleOAuthController(ITenantGoogleAuthenticationService service, ITenantService tenantService, IGoogleAuthService googleAuthService)
    {
        this.service = service;
        this.tenantService = tenantService;
        this.googleAuthService = googleAuthService;
    }
    public async Task<ActionResult> Callback(GoogleAuthResponse model)
    {
        try
        {
            var response = await this.googleAuthService.AccessToken(model.Code);

            var qs = HttpUtility.ParseQueryString(model.State);
            var appid = qs["appid"];
            var tenant = await this.tenantService.TenantByAppId(appid);
            var webTenant = await this.tenantService.GetWebTenant(appid);
            var result = await this.service.GoogleAuthenticationSave(new TenantGoogleAuthenticationViewModel
            {
                AccessToken = response.AccessToken,
                Expires = DateTime.Now.AddSeconds(response.ExpiresIn),
                RefreshToken = response.RefreshToken,
                TenantId = tenant.Id
            }, webTenant);
            return new RedirectResult("/");
        }
        catch (Exception ex)
        {
            return Content(ex.Message);
        }
    }
}

Model for what is sent to you in the Callback

GoogleAuthResponse

public class GoogleAuthResponse
{
    public string State { get; set; }
    public string Code { get; set; }
    public string Scope { get; set; }
}

Don't worry about the Tenant code as that is specific to my system and shouldn't have any bearing on this implementation. I use the "appid" to identify the users of my application and google is nice enough to allow me to pass that to them in the AuthorizationUrl and they nicely pass it back to me.

So basically you make a call to the GoogleAuthService.AuthorizationUrl() to get the URL. Redirect the User to that URL. Make sure you setup a ga:scopes in your Web.config. When the user agrees to all your security requests they will be forwarded back to the GoogleOAuthController and hit the Callback action, where you will take the code and exchange it for an access_token. At this point you can do like I do and just save it to your database so you can use it later. Looks like by default it expires in like an hour, so you will most likely be calling a RefreshToken before every use but that is up to you.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for your response. I will take a look at it all when I get a chance.
Oh well, it turns out that despite of a lot of time being put into this, they decided to make a more crude solution for the time being using smtp and pop, in order to make things moving. I will most likely be asked to look into this again at a later time, but that is not for me to decide. For the time being I have to move on. Thanks though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.