First you need to obtain Client ID, Secret & Redirect URI:
- Go to the Credentials page.
- Click Create credentials > OAuth client ID.
- Select UWP application type and provide Store ID of your app (find it in Microsoft Partner Center under App Identity section.
- Then you get you Client ID and Client Secret.
- Most important part - create the right Redirect URI. For example, if your Client ID is
123456789-abcdefgh.apps.googleusercontent.com
then your redirect URI will be com.googleusercontent.apps.123456789-abcdefgh:/oauth2redirect
. So you need to reverse Client ID and add an optional path (you can use whatever path you want instead of /oath2redirect).
Now using the class posted below you can get Access Token in the following way:
internal class GoogleLoginHelper
{
private const string CLIENT_ID = "PUT YOUR CLIENT ID HERE";
private const string CLIENT_SECRET = "PUT YOUR SECRET HERE";
private const string TOKEN_ENDPOINT = "https://www.googleapis.com/oauth2/v4/token";
private const string REDIRECT_URI = "PUT YOUR REDIRECT URI HERE"; // reverce clientID + optional path
private HttpClient _httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = true });
public async Task<string> GetAccessToken()
{
// Generates state and PKCE values.
string state = RandomDataBase64url(32);
string code_verifier = RandomDataBase64url(32);
string code_challenge = Base64urlencodeNoPadding(Sha256(code_verifier));
const string code_challenge_method = "S256";
string authString = "https://accounts.google.com/o/oauth2/auth?client_id=" + CLIENT_ID;
authString += "&scope=profile%20email";
authString += $"&redirect_uri={Uri.EscapeDataString(REDIRECT_URI)}";
authString += $"&state={state}";
authString += $"&code_challenge={code_challenge}";
authString += $"&code_challenge_method={code_challenge_method}";
authString += "&response_type=code";
var receivedData = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
new Uri(authString),
new Uri(REDIRECT_URI));
switch (receivedData.ResponseStatus)
{
case WebAuthenticationStatus.Success:
return await GetAccessToken(receivedData.ResponseData, state, code_verifier);
case WebAuthenticationStatus.ErrorHttp:
var err = $"HTTP error: {receivedData.ResponseErrorDetail}";
Debug.WriteLine(err);
return null;
case WebAuthenticationStatus.UserCancel:
default:
return null; // Login cancelled
}
}
// Private
private async Task<string> GetAccessToken(string data, string expectedState, string codeVerifier)
{
// Parses URI params into a dictionary - ref: https://mcmap.net/q/121346/-easiest-way-to-parse-quot-querystring-quot-formatted-data
var p = data.IndexOf("?");
if (p == -1) { return null; }
data = data.Substring(p + 1);
Dictionary<string, string> queryStringParams = data.Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
if (queryStringParams.ContainsKey("error"))
{
Debug.WriteLine($"OAuth error: {queryStringParams["error"]}.");
return null;
}
if (!queryStringParams.ContainsKey("code") || !queryStringParams.ContainsKey("state"))
{
Debug.WriteLine($"Wrong response {data}");
return null;
}
if (queryStringParams["state"] != expectedState)
{
Debug.WriteLine($"Invalid state {queryStringParams["state"]}");
return null;
}
var content = new StringContent(
$"code={queryStringParams["code"]}&client_secret={CLIENT_SECRET}&redirect_uri={Uri.EscapeDataString(REDIRECT_URI)}&client_id={CLIENT_ID}&code_verifier={codeVerifier}&grant_type=authorization_code",
Encoding.UTF8,
"application/x-www-form-urlencoded");
HttpResponseMessage response = await _httpClient.PostAsync(TOKEN_ENDPOINT, content);
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Debug.WriteLine("Authorization code exchange failed.");
return null;
}
JsonObject tokens = JsonObject.Parse(responseString);
var accessToken = tokens.GetNamedString("access_token");
return accessToken;
}
// Helper methods
/// <summary>
/// Base64url no-padding encodes the given input buffer.
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public static string Base64urlencodeNoPadding(IBuffer buffer)
{
string base64 = CryptographicBuffer.EncodeToBase64String(buffer);
// Converts base64 to base64url.
base64 = base64.Replace("+", "-");
base64 = base64.Replace("/", "_");
// Strips padding.
base64 = base64.Replace("=", "");
return base64;
}
/// <summary>
/// Returns URI-safe data with a given input length.
/// </summary>
/// <param name="length">Input length (nb. output will be longer)</param>
/// <returns></returns>
public static string RandomDataBase64url(uint length)
{
IBuffer buffer = CryptographicBuffer.GenerateRandom(length);
return Base64urlencodeNoPadding(buffer);
}
/// <summary>
/// Returns the SHA256 hash of the input string.
/// </summary>
/// <param name="inputString"></param>
/// <returns></returns>
public static IBuffer Sha256(string inputString)
{
HashAlgorithmProvider sha = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
IBuffer buff = CryptographicBuffer.ConvertStringToBinary(inputString, BinaryStringEncoding.Utf8);
return sha.HashData(buff);
}
}
NOTE 1:
My code returns you an Access Token. Reference to Romasz answer / source code if you need to retrieve user info, Refresh Token or do more things (but keep in mind that you will need Access Token anyway).
NOTE 2:
My answer based on Romasz, with some corrections for 2023. The main difference is that you now can't use OOB method like in the orginal answer. This means you can't use callback URL urn:ietf:wg:oauth:2.0:oob
because Google discontinued it. Other minor fixes were made to make it works with Custom URL Scheme.
OLD ANSWER - DO NOT USE (won't work)
The answer below doesn't work anymore - see more details here.
I'm using pretty straightforward code with Google.Apis.Oauth2.v2
Nuget package. Note, that I'm using v.1.25.0.859 of that package. I tried to update to the lastest version (1.37.0.1404), but this surprisingly doesn't work with UWP. At the same time v. 1.25.0.859 works just fine.
So, unless there's a better option, I would recommend to use a bit old, but working version of Nuget package.
This is my code:
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/User/Auth/google_client_secrets.json"),
new[] { "profile", "email" },
"me",
CancellationToken.None);
await GoogleWebAuthorizationBroker.ReauthorizeAsync(credential, CancellationToken.None);
Then you can retrieve access token from: credential.Token.AccessToken
.