Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I not receiving a RefreshToken from a Google OAuth request?

I am attempting to integrate Google Calendar into my application and I am having some problems with the OAuth authorization passing off a RefreshToken. I receive an AccessToken with no issue, but the RefreshToken property is null. See the line marked "ERROR HERE:" for where I am having the issue

My Asp.Net MVC controller (named OAuthController) looks like the following:

    public ActionResult Index()
    {
        var client = CreateClient();
        client.RequestUserAuthorization(new[] { "https://www.googleapis.com/auth/calendar" }, new Uri("http://localhost/FL.Evaluation.Web/OAuth/CallBack"));

        return View();
    }

    public ActionResult CallBack()
    {

        if (string.IsNullOrEmpty(Request.QueryString["code"])) return null;

        var client = CreateClient();

        // Now getting a 400 Bad Request here
        var state = client.ProcessUserAuthorization();

        // ERROR HERE:  The RefreshToken is NULL
        HttpContext.Session["REFRESH_TOKEN"] = Convert.ToBase64String(Encoding.Unicode.GetBytes(state.RefreshToken));

        return JavaScript("Completed!");
    }

    private static WebServerClient CreateClient()
    {
        return
            new WebServerClient(
                new AuthorizationServerDescription()
                    {
                        TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"),
                        AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
                        ProtocolVersion = ProtocolVersion.V20
                    }
                , _GoogleClientId, _GoogleSecret);
    }

I see in Google's API documents, that I need to ensure that the access_type requested is set to offline for a RefreshToken to be sent. How do I set this value in my Authenticator request?

like image 825
Jeff Fritz Avatar asked Jul 05 '12 14:07

Jeff Fritz


People also ask

How can I get email from Google OAuth?

If this scope is included while you generate the refresh token, you should be able to get the email address of the authenticating user by making the following request: https://www.googleapis.com/oauth2/v2/userinfo?access_token="YOUR_ACCESS_TOKEN". You can try this out in the API explorer.

How do I get an access token response?

If the token access request is invalid or unauthorized, then the authorization server returns an error response. The access token is given by the authorization server when it accepts the client ID, client password and authorization code sent by the client application.

How do I get the OAuth refresh token?

To get a refresh token, you must include the offline_access scope when you initiate an authentication request through the /authorize endpoint. Be sure to initiate Offline Access in your API. For more information, read API Settings.


2 Answers

After hours of fiddling with DotNetOpenAuth and the Google APIs published for .Net, I got nowhere fast. I decided to circumvent the libraries and went directly at the Google REST API with native HttpRequest and HttpResponse objects. My sanitized code for my MVC controller follows:

    private static string _GoogleClientId = "CLIENT_ID";
    private static string _GoogleSecret = "SECRET";
    private static string _ReturnUrl = "http://localhost/OAuth/CallBack";

    public ActionResult Index()
    {
        return Redirect(GenerateGoogleOAuthUrl());
    }

    private string GenerateGoogleOAuthUrl()
    {

        //NOTE: Key piece here, from Andrew's reply -> access_type=offline forces a refresh token to be issued
        string Url = "https://accounts.google.com/o/oauth2/auth?scope={0}&redirect_uri={1}&response_type={2}&client_id={3}&state={4}&access_type=offline&approval_prompt=force";
        string scope = UrlEncodeForGoogle("https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.readonly").Replace("%20", "+");
        string redirect_uri_encode = UrlEncodeForGoogle(_ReturnUrl);
        string response_type = "code";
        string state = "";

        return string.Format(Url, scope, redirect_uri_encode, response_type, _GoogleClientId, state);

    }

    private static string UrlEncodeForGoogle(string url)
    {
        string UnReservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
        var result = new StringBuilder();

        foreach (char symbol in url)
        {
            if (UnReservedChars.IndexOf(symbol) != -1)
            {
                result.Append(symbol);
            }
            else
            {
                result.Append('%' + String.Format("{0:X2}", (int)symbol));
            }
        }

        return result.ToString();
    }

    class GoogleTokenData
    {
        public string Access_Token { get; set; }
        public string Refresh_Token { get; set; }
        public string Expires_In { get; set; }
        public string Token_Type { get; set; }
    }

    public ActionResult CallBack(string code, bool? remove)
    {

        if (remove.HasValue && remove.Value)
        {
            Session["GoogleAPIToken"] = null;
            return HttpNotFound();
        }

        if (string.IsNullOrEmpty(code)) return Content("Missing code");

        string Url = "https://accounts.google.com/o/oauth2/token";
        string grant_type = "authorization_code";
        string redirect_uri_encode = UrlEncodeForGoogle(_ReturnUrl);
        string data = "code={0}&client_id={1}&client_secret={2}&redirect_uri={3}&grant_type={4}";

        HttpWebRequest request = HttpWebRequest.Create(Url) as HttpWebRequest;
        string result = null;
        request.Method = "POST";
        request.KeepAlive = true;
        request.ContentType = "application/x-www-form-urlencoded";
        string param = string.Format(data, code, _GoogleClientId, _GoogleSecret, redirect_uri_encode, grant_type);
        var bs = Encoding.UTF8.GetBytes(param);
        using (Stream reqStream = request.GetRequestStream())
        {
            reqStream.Write(bs, 0, bs.Length);
        }

        using (WebResponse response = request.GetResponse())
        {
            var sr = new StreamReader(response.GetResponseStream());
            result = sr.ReadToEnd();
            sr.Close();
        }

        var jsonSerializer = new JavaScriptSerializer();
        var tokenData = jsonSerializer.Deserialize<GoogleTokenData>(result);
        Session["GoogleAPIToken"] = tokenData.Access_Token;

        return JavaScript("Refresh Token: " + tokenData.Refresh_Token);

    }

Big thanks to Kelp for a bit of the code in this snippet.

like image 74
Jeff Fritz Avatar answered Oct 21 '22 15:10

Jeff Fritz


Adjust GoogleAuthenticationServer.Description to have an authorization endpoint URI that includes ?access_type=offline in the query string.

like image 30
Andrew Arnott Avatar answered Oct 21 '22 15:10

Andrew Arnott