Authenticating Your C# Application for the Google Cloud Platform – Part 2

By in
No comments

Now that we understand how to authenticate a C# application to work with your own project’s GCP resources, let’s take it a step further. In this post, we’ll look at how we can combine that connectivity with end user data, via three-legged OAUTH authorization, to perform operations on user content, with their permission of course.

For this example, I’ve created a very simple ASP.NET MVC project that logs a user in, gets their profile image, and uploads it to a specific bucket. Let’s take a closer look at each part.

Creating API Credentials

Before we can ask a user for permission, we have to first configure our application with a Client ID and Secret so that we have the necessary elements to complete the OAUTH workflow. This is done by visiting the API Console and creating a new OAUTH Client ID:

Google-Cloud-Platform-Create-OAUTH-Client-ID

If you haven’t already configured your OAUTH consent screen, you’ll be prompted to do so. This is required because the authorization request needs to show the user the name of your application, so that it’s clear that you are the one asking for permission:

Google-Cloud-Platform-OAUTH-Consent-Screen

Now you can proceed to configure the new key, in this case for a Web Application. You need to specify a valid path (within your website) to receive the callback redirect URI to send the user after they grant permission:

Google-Cloud-Platform-Create-OAUTH-Client-ID-Credentials

For our application, this is represented by the path: http://localhost:4078/Login/Callback.

After a user authorizes our app to access their account, Google will redirect them back to this path to complete the OAUTH workflow, sending with it a code that we need to validate the request (more on this later).

Once this is configured, you’ll be given a Client ID and Secret that you’ll need to safely store where it’s accessible only by your site (and not checked into source control or otherwise published!).

Google-Cloud-Platform-Client-ID-and-Secret

To keep things simple we’ll embed them in our code, but you’ll definitely want to protect this on a production site.

Authenticating the User

Now that our application is configured to properly request for access, we need to go ahead and create a way to initialize the OAUTH workflow for our users.

I added a simple LoginController that takes care of asking the user to authorize our application to access their account. This controller simply constructs the authorization URL on the trusted Google domain that the user will use to login and authorize the app.

Here’s what the code looks like for the Login Controller with this default action to start the authentication process:

public class LoginController : Controller
    {
        // CLIENT ID AND SECRETSHOULD NOT BE STORED IN CODE OR CONFIG, use Default Credentials or other safe method
        private const string CLIENT_ID = "[CLIENT-ID-KEY-FROM-API-MANAGER]";
        private const string CLIENT_SECRET = "[CLIENT-SECRET-KEY-FROM-API-MANAGER";

        // the bucket we're using to store images
        private const string BUCKET_NAME = "falafel-test-profile-images";

        public ActionResult Index()
        {
            // Build the login oauth url
            var scopes = "email profile";                               // what we want to access
            var redirectUrl = "http://localhost:4078/Login/Callback";   // the url to which Google should send the user back to complete authentication
            var clientID = CLIENT_ID;                                   // SHOULD BE IN A SAFE PLACE, NOT HERE!

            // redirect user to the login url
            var oauthUrl = string.Format("https://accounts.google.com/o/oauth2/v2/auth?scope={0}&redirect_uri={1}&response_type=code&client_id={2}", scopes, redirectUrl, clientID);
            return Redirect(oauthUrl);
        }
    }

When the user visits this page, they’ll be redirected to the constructed url, which will proceed to ask them to login and authorize your application (using the name you configured on the consent screen):

Google-Cloud-Platform-Authorize-User-Account

Once they grant access, Google will redirect them back to your site via the Callback URI that you configured when setting up the API credentials. It’s our job to take that code and validate it by posting it (along with our app Secret and other parameters that validate the code) to another Google endpoint.

Here’s the code for the Callback action that handles this request:

public async Task<ActionResult> Callback(string code)
        {
            // build the request to validate the incoming code
            var clientID = CLIENT_ID;                                   // from the Google API console, SHOULD BE IN A SAFE PLACE, NOT HERE!
            var clientSecret = CLIENT_SECRET;                           // from the Google API console, SHOULD BE IN A SAFE PLACE, NOT HERE!
            var redirectUri = "http://localhost:4078/Login/Callback";   // the original url we sent must match what we original set as the callback
            var grantType = "authorization_code";                       // this tells OAUTH we're using a code to validate


            // wrap parameters in a Form object
            var content = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("code", code),   // the code we got from the callback
                new KeyValuePair<string, string>("client_id", clientID),
                new KeyValuePair<string, string>("client_secret", clientSecret),
                new KeyValuePair<string, string>("redirect_uri", redirectUri),
                new KeyValuePair<string, string>("grant_type", grantType),
            });

            // the url to send the POST request (from the google docs)
            var postUrl = "https://www.googleapis.com/oauth2/v4/token";

            // submit the request
            var client = new HttpClient();
            var result = await client.PostAsync(postUrl, content);

            // get the result as a string
            var resultContent = await result.Content.ReadAsStringAsync();

            // parse the result into an object
            var resultObject = new { access_token = "" };
            var json = JsonConvert.DeserializeAnonymousType(resultContent, resultObject);

            // use the token to get the user's profile
            var url = "https://www.googleapis.com/oauth2/v1/userinfo";
            client.DefaultRequestHeaders.Add("Authorization", "Bearer " + json.access_token);
            var response = await client.GetStringAsync(url);

            // parse the new result to get the profile
            var profileResultObject = new { picture = "", id = "" };
            var profileJson = JsonConvert.DeserializeAnonymousType(response, profileResultObject);

            // upload the image to the storage bucket
            await UploadProfileImage(profileJson.picture, profileJson.id);

            // redirect to a result page
            return RedirectToAction("Images", "Home");
        }

Once we have the user’s permission (via the access_token property) we can use that to get their profile information, including their profile image. We use the Google Cloud C# API (authorizing as we described in the previous topic) to upload that profile (via a Stream object) directly to our storage buckets.

This is the code for the UploadProfileImage method that performs the upload to Google storage:

private async Task UploadProfileImage(string url, string id)
        {
            // get the keyfile
            var path = Server.MapPath("~/Falafel Test Project-b53a21babe66.json");

            // authorize a client
            var credential = GoogleCredential.FromStream(new FileStream(path, FileMode.Open)).CreateScoped(new string[] { StorageService.Scope.DevstorageReadWrite });
            var storageClient = StorageClient.Create(credential);

            // set the destination bucket, file name and content type
            var destination = new Google.Apis.Storage.v1.Data.Object();
            destination.Bucket = BUCKET_NAME;
            destination.ContentType = "image/jpeg";
            destination.Name = id + ".jpg";
            
            // get the image stream
            var httpClient = new HttpClient();
            var resultStream = await httpClient.GetStreamAsync(url);

            // upload the stream to the destination bucket as public
            storageClient.UploadObject(destination, resultStream, new UploadObjectOptions() { PredefinedAcl = PredefinedObjectAcl.PublicRead });
        }

Finally, we redirect the user to new action on the Home controller that uses the same authentication to read the images uploaded to the bucket and show them to the user. Here is the code:

public ActionResult Images()
        {

            // the bucket we're using to store images
            var BUCKET_NAME = "falafel-test-profile-images";

            // get the keyfile
            var path = Server.MapPath("~/Falafel Test Project-b53a21babe66.json");

            // authorize a client
            var credential = GoogleCredential.FromStream(new FileStream(path, FileMode.Open)).CreateScoped(new string[] { StorageService.Scope.DevstorageReadOnly });
            var storageClient = StorageClient.Create(credential);

            PagedEnumerable<Objects, Google.Apis.Storage.v1.Data.Object> images = storageClient.ListObjects(BUCKET_NAME);

            return View(images);
        }

And here is what the resulting page looks like after our first user logs in:

Google-Cloud-Platform-Profile-Image-Result

Caveats and Wrapping Up

Of course, since this is a very simple example, we haven’t done any error checking (such as if the user has already uploaded their image), nor do we do any file locking to ensure multiple people aren’t attempting to read the key file (the result of which should probably be cached) which would probably cause an exception. If you were deploying this to a GCE or GAE instance, you could probably leverage the Default Application Credentials (from the previous topic) to authorize access to your storage account (which is certainly the recommended option)!

Finally, of course, we are embedding the Client ID and Secrets right inside our code, which is certainly a security risk and should never be done on a production site.

However, this hopefully shows how easy it is to authenticate a custom ASP.NET website to use both GCP services as well as client user accounts together to create a solution entirely driven by Google APIs.

A more thorough look at Google Storage and other Google services is coming soon, so stay tuned for more. In the meantime, as always, I hope this was helpful, and thanks for reading!

The following two tabs change content below.

selaromdotnet

Senior Developer at iD Tech
Josh loves all things Microsoft and Windows, and develops solutions for Web, Desktop and Mobile using the .NET Framework, Azure, UWP and everything else in the Microsoft Stack. His other passion is music, and in his spare time Josh spins and produces electronic music under the name DJ SelArom. His other passion is music, and in his spare time Josh spins and produces electronic music under the name DJ SelArom.