It’s no secret that I love Windows, and especially writing UWP apps for Windows using C#. In fact I’ve written a bit about creating UWP apps in the past, so as a fan of UWP, I wanted to explore how one can leverage the Google Cloud Platform and other Google services in Windows apps.
Unfortunately, as of this writing, Google does not officially support UWP. However, this is not to say you cannot USE Google services in your app; it only means that the SDKs available to simplify accessing these services have not yet been updated to support the UWP platform.
In short, we have to do all the work ourselves!
Fortunately, there’s a few resources out there that I’ve compiled here to get you started. Let’s start with a simple example: authorizing an app to access a Google user account.
Google Samples for Windows and UWP
A good place to start is going to be on Github, where there is a collection of OAuth samples for Windows. These apps show you how to authenticate users in different Windows apps, including console apps, WPF, and UWP, which is what we’re interested in today.
The README for the UWP app describes the project in more detail, including how to get started, so I’ll leave it to the reader to check that out. Instead, I want to review a few important points about how this project is setup.
Create OAuth Credentials
Before getting started you want to make sure you have created OAUTH credentials for your application, as you’ll need a Client ID specifically setup for this authorization method for any of this to work.
The README for the sample specifies that you need to follow the instructions for the installed app specifically for iOS, as this works for UWP apps as well. So create your OAUTH key and note your Client ID which you’ll need to update in the sample.
Authorization Workflow
The way this sample was written is quite similar to the sample we looked at previously for authorizing a website to use a Google user account. Essentially, an authorization URL is constructed (using client id, and some other required parameters) and the user is redirected to that page via the browser.
This means the app will actually launch the browser on the device, taking the user out of the app to complete the login process.
![Google-Cloud-Platform-UWP-OAuth-Sample-Launch-Browser]images/Google-Cloud-Platform-UWP-OAuth-Sample-Launch-Browser.png)
So let’s take a look at the code that accomplishes this:
/// <summary>
/// Starts an OAuth 2.0 Authorization Request.
/// </summary>
private void button_Click(object sender, RoutedEventArgs e) {
// 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";
// Stores the state and code_verifier values into local settings.
// Member variables of this class may not be present when the app is resumed with the
// authorization response, so LocalSettings can be used to persist any needed values.
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
localSettings.Values["state"] = state;
localSettings.Values["code_verifier"] = code_verifier;
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}", authorizationEndpoint, System.Uri.EscapeDataString(redirectURI), clientID, state, code_challenge, code_challenge_method); output("Opening authorization request URI: " + authorizationRequest);
// Opens the Authorization URI in the browser.
var success = Windows.System.Launcher.LaunchUriAsync(new Uri(authorizationRequest));
}
Although the process is similar to the web workflow we looked at for the MVC sample, we are using a slightly different URL and parameters to indicate that the request is coming from a device. For security reasons, we can’t embed our client secret in the app, because it could be sniffed over the wire when we POST it to exchange it for the access token.
Instead, the UWP sample leverages Proof Key Code Exchange (PKCE) to validate the request. Essentially, rather than sending the client secret, we hash a code in the app so that the server can know that the request came from us.
If you want to know more about this setup there’s a great description of the process on the Auth0 website: https://auth0.com/docs/api-auth/grant/authorization-code-pkce
Once the user completes the login, they are redirected to our app via the protocol activation defined in the manifest:
![Google-Cloud-Platform-UWP-Manifest-Capabilities]images/Google-Cloud-Platform-UWP-Manifest-Capabilities.png)
Launching the app registers this protocol on the device so that when the browser redirects the user to it the app is launched, passing along the OAUTH parameters so we can continue the login process.
![Google-Cloud-Platform-UWP-Protocol-Activation]images/Google-Cloud-Platform-UWP-Protocol-Activation.png)
In this case, launching the app again triggers the OnNavigatedTo event, which checks for the presence of a URI parameter, which is then parsed to validate the result so far:
/// <summary>
/// Processes the OAuth 2.0 Authorization Response
/// </summary>
/// <param name="e"></param>
protected override void OnNavigatedTo(NavigationEventArgs e) {
if (e.Parameter is Uri) {
// Gets URI from navigation parameters.
Uri authorizationResponse = (Uri) e.Parameter;
string queryString = authorizationResponse.Query;
output("MainPage received authorizationResponse: " + authorizationResponse);
// Parses URI params into a dictionary
// ref: http://stackoverflow.com/a/11957114/72176
Dictionary<string, string> queryStringParams = queryString.Substring(1).Split('&')
.ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
if (queryStringParams.ContainsKey("error")) {
output(String.Format("OAuth authorization error: {0}.", queryStringParams["error"]));
return;
}
if (!queryStringParams.ContainsKey("code") || !queryStringParams.ContainsKey("state")) {
output("Malformed authorization response. " + queryString);
return;
}
// Gets the Authorization code & state
string code = queryStringParams["code"];
string incoming_state = queryStringParams["state"];
// Retrieves the expected 'state' value from local settings (saved when the request was made).
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
string expected_state = (String) localSettings.Values["state"];
// Compares the receieved state to the expected value, to ensure that
// this app made the request which resulted in authorization
if (incoming_state != expected_state) {
output(String.Format("Received request with invalid state ({0})", incoming_state));
return;
}
// Resets expected state value to avoid a replay attack.
localSettings.Values["state"] = null;
// Authorization Code is now ready to use!
output(Environment.NewLine + "Authorization code: " + code);
string code_verifier = (String) localSettings.Values["code_verifier"];
performCodeExchangeAsync(code, code_verifier);
} else {
Debug.WriteLine(e.Parameter);
}
}
If everything checks out, we can finally exchange the resulting code for an access token, being sure to pass along the original code verifier so that the server can validate the request.
Here is what that code looks like:
async void performCodeExchangeAsync(string code, string code_verifier) {
// Builds the Token request
string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&scope=&grant_type=authorization_code", code, System.Uri.EscapeDataString(redirectURI), clientID, code_verifier);
StringContent content = new StringContent(tokenRequestBody, Encoding.UTF8, "application/x-www-form-urlencoded");
// Performs the authorization code exchange.
HttpClientHandler handler = new HttpClientHandler();
handler.AllowAutoRedirect = true;
HttpClient client = new HttpClient(handler);
output(Environment.NewLine + "Exchanging code for tokens...");
HttpResponseMessage response = await client.PostAsync(tokenEndpoint, content);
string responseString = await response.Content.ReadAsStringAsync();
output(responseString);
if (!response.IsSuccessStatusCode) {
output("Authorization code exchange failed.");
return;
}
// Sets the Authentication header of our HTTP client using the acquired access token.
JsonObject tokens = JsonObject.Parse(responseString);
string accessToken = tokens.GetNamedString("access_token");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
// Makes a call to the Userinfo endpoint, and prints the results.
output("Making API Call to Userinfo...");
HttpResponseMessage userinfoResponse = client.GetAsync(userInfoEndpoint).Result;
string userinfoResponseContent = await userinfoResponse.Content.ReadAsStringAsync();
output(userinfoResponseContent);
}
As you can see at the end of that sample, we use the resulting access_token as a Bearer token to finally request the user information. Because there isn’t any helper libraries or SDK to wrap access to Google services, we have to do it ourselves once again via a simple HTTP REST call to the user endpoint which is defined in the app:
const string userInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo";
In the end, the result is the same, and we can now review the user’s profile information just like before, which the app outputs as text:
![Google-Cloud-Platform-UWP-Sample-Result]images/Google-Cloud-Platform-UWP-Sample-Result.png)
Although this works just fine, the authentication process demonstrated by the sample is obviously a bit cumbersome. Launching the browser and leaving the app is not an ideal experience for the user.
Fortunately, we can fix that by leveraging a helpful class from the .NET framework.
WebAuthenticationBroker
The WebAuthenticationBroker class exposes the static method AuthenticateAsync that accepts the same authorization URL that previously was launched by the browser. The difference here is that instead of leaving the app to authorize, it pops up an embedded browser so that you can complete the authorization from within the app itself.
A more complete description of this process is in the MSDN docs: Web authentication broker
For this to work, however, we do have to pass a URI that makes sense for the identity provider to redirect back to the app such that the broker can continue the process and hand the result back to the app.
While I began putting this together, I actually discovered that another developer LorenzCK has already created such an update, and has created a pull request to the Google sample that updates the sample to use this broker. Many thanks to the developer for sharing this code!
Replacing the MainPage.xaml.cs of the current sample with the updated code simplifies things quite a bit. Because we no longer leave the app to authenticate the user, we don’t need to save any of the generated values to local storage, and can handle the entire operation via the async authorize method of the broker.
private async void button_Click(object sender, RoutedEventArgs e) {
// 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";
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}", authorizationEndpoint, System.Uri.EscapeDataString(redirectURI), clientID, state, code_challenge, code_challenge_method);
output("Opening authorization request URI: " + authorizationRequest);
var result = await
WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, new Uri(authorizationRequest), new Uri(authorizationCompleteEndPoint));
output("WebAuthenticationBroker result: " + result.ResponseStatus);
switch (result.ResponseStatus) {
case WebAuthenticationStatus.Success:
string data = result.ResponseData;
// Strip authentication result from data and process rest of encoded data.
ProcessAuthorization(data.Substring(data.IndexOf(' ') + 1), state, code_verifier);
break;
case WebAuthenticationStatus.ErrorHttp:
output("HTTP error: " + result.ResponseErrorDetail);
break;
case WebAuthenticationStatus.UserCancel:
break;
}
}
Once a successful result is returned from the operation, we can parse the result, validate like we did before, and exchange the code for an access token:
/// <summary>
/// Processes the OAuth 2.0 Authorization Response
/// </summary>
/// <param name="data">Incoming data formatted as a query string</param>
/// <param name="expected_state">Expected state value to verify that this app has initiated authentication</param>
private void ProcessAuthorization(string data, string expected_state, string code_verifier) {
output("MainPage received authorizationResponse: " + data);
// Parses URI params into a dictionary
// ref: http://stackoverflow.com/a/11957114/72176
Dictionary<string, string> queryStringParams = data.Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
if (queryStringParams.ContainsKey("error")) {
output(String.Format("OAuth authorization error: {0}.", queryStringParams["error"]));
return;
}
if (!queryStringParams.ContainsKey("code") || !queryStringParams.ContainsKey("state")) {
output("Malformed authorization response. " + data);
return;
}
// Gets the Authorization code & state
string code = queryStringParams["code"];
string incoming_state = queryStringParams["state"];
// Compares the received state to the expected value, to ensure that
// this app made the request which resulted in authorization
if (incoming_state != expected_state) {
output(String.Format("Received request with invalid state ({0})", incoming_state));
return;
}
// Authorization Code is now ready to use!
output(Environment.NewLine + "Authorization code: " + code);
performCodeExchangeAsync(code, code_verifier); }
In addition, we no longer have to use the protocol activation, since we never leave the app, so this setting can be removed from the manifest as well. Instead, the return uri uses this format:
const string redirectURI = "urn:ietf:wg:oauth:2.0:oob:auto";
This tells Google to return the result to the title bar of the browser (in this case, the WebAuthenticationBroker). More information on the different options for redirect URI is here: https://developers.google.com/api-client-library/python/auth/installed-app#choosingredirecturi
Now when we run the app and click the button to login, the web broker launches inside the app, where we can safely provide our Google credentials and authorize it:
Authorizing the application closes the broker and processes the result just like before, showing the same user profile information in the app, all without ever leaving it!
Wrapping up and Next Steps
The Google Windows OAuth Samples are a great way to get started adding Google services to your Windows 10 apps by easily allowing users to authorize access to their accounts. Thanks to the efforts of LorenzCK we can take this a step further and leverage the frameworks WebAuthenticationBroker to simplify this process even further, resulting in a better experience for the user.
However, now that we’ve authorized a user account so we can access Google services, we need to be able to take it a step further, authorizing our own app to access our Google Cloud Platform services.
Fortunately, this is also possible, and will the subject of our next post. Until then as always, I hope this was helpful and thanks for reading!