Google OAuth 2.0

Following documentation, a prototype has been built in order to understand better how to integrate with Google OAuth.

The prototype itself is an ASP.NET Core Restful Web API built on .NET 9. The repository can be found at GitHub.

Google has different levels of access, and it has a clear separation between Authorization and Authentication. But this application itself only leverages half of what Google might offer, since we simply want for google to take care of the authentication flow for us through a Google Account, we will be using a web client type for the Google exchange that takes place.

The steps that one would normally take for a web client authentication flow will be:

  1. Your application (UI) redirects a browser to a Google URL; the URL includes query parameters that indicate the type of access being requested.

  2. Google handles the user authentication, session selection, and user consent. The result is an authorization code, which the application can exchange for an access token and refresh token.

  3. The application gets an authorization code that we can later exchange for an access token and a refresh token.

  4. The application should store the refresh token for future use and use the access token to access a Google API. Once the access token expires, the application uses the refresh token to obtain a new one.

Google OAuth Setup

The tutorials that were used in order to find out how to integrate with Google’s OAuth were:

First of all, a Google Cloud Project should be created in order to tie it to applications, and other integrations with all the services that Google Cloud offers. A KakeiBro project has been created and that will be the nexus into Google OAuth.

Due to us having to integrate with Google Cloud, we have to keep some sort of data that can help us keep track of the user that’s on Google Cloud, and that be tied into our own data. Hence, we will just make requests to Google, the user will authorize the usage of his account and on his behalf we will be then get some metadata from Google and use that to then concretize the user registration.

Meaning we will have to make do with what Google has and we create on the background all the data from the user. It’s basically a facade in which we make use of Google for the Auth, if everything is okay from their side, we simply get back tokens, enable a bit of logic to then use tokens and refresh tokens accordingly.

It would probably be a good idea to leverage Redis and its TTL setting on a record to invalidate the token once everything expires. Also, we can leverage Redis and some key in there to retrieve the token if it is still valid (send it to the client and then have the client not cache it in local storage, but the database always returning that and then the client saving it into memory if it doesn’t find it).

Once the project is created, you have to configure the Google Auth Platform. Which is one of the services at the Google Cloud Console. It’s here that you will have to setup the service so that we can get a client secret, and a client ID in order to start consuming the application. Luckily the setup process is pretty self-explanatory so just follow the steps.

Once that’s done, you need to get a Client that will be under the project. However for this you have to configure the Consent Screen. This requires a logo, alongside links for an Application home page, a privacy policy link, and a ToS link. Make sure to add all the domains that are used for these links to be also added as part of the Authorized Domains section.

After this is done, you might have to wait a bit until the consent page state is updated, but once it is you can go into the Clients tab and then create a client that should be for web, and it will ask for information in regards to the app, alongside the callback URI and the javascript sources that will be authorized. Once the client is created, you can go and download all the secrets in a .json format. This is extremely sensitive information, so add that to a .gitignore and never commit them, there’s a secret, and other information specifically catered to authenticate the client when trying to access the Google Auth API.

Usage of APIs for authentication

The base documentation used for this was:

  • POST https://accounts.google.com/o/oauth2/v2/auth Is the first endpoint that should be called when initializing the flow. In here there are different query params that can be sent.

    Parameter Description

    client_id

    Required Client ID for the application this comes from the Clients page at Google Console.

    redirect_uri

    Required Determines where the API server redirects the browser after the auth flow is completed. The value must match exactly one of the authorized redirect URIs on the OAuth 2.0 client. If this doesn’t match you will get a redirect_uri_mismatch. http or https scheme, case, and trailing slash ('/') must all match.

    scope

    Required A space-delimited list of scopes that identify the resources that your application could access on the user’s behalf. These values inform the google consent screen that Google displays to the user. In order for us to hit and get user’s information, we should be sending openid email profile

    response_type

    Required Determines whether the Google OAuth 2.0 endpoint returns an authorization code. Set the parameter value to code for web server applications.

    access_type

    Recommended Indicates whether your application can refresh access tokens when the user is not present at the browser. Valid parameters are online (default) and offline. (This means whether if we get a refresh token or not)

    prompt

    Recommended In order for the user to have the application authorized again, this also allows for subsequent attempts on the flow to always return a refresh token, for that to happen it should be set to consent.

    The web hook will receive the parameters such as (http://localhost:5080/api/google-auth-callback):

    Parameter Description

    code

    The authorization code that will be granted on behalf of the user in order to get access tokens.

    scope

    A space separated list of the scopes that will be granted through this code’s redeem.

    authuser

    By default 0

    prompt

    By default none, but depending on the initial Auth endpoint it can change to consent

  • POST https://oauth2.googleapis.com/token Is the second endpoint, that will have the auth code sent to. It should be fed query params as such:

    Parameter Description

    code

    The auth code that will be used in the endpoint to get the tokens

    client_id

    The client ID obtained from the Cloud Console at Clients

    client_secret

    The client secret obtained from the Cloud Console at Clients

    redirect_uri

    One of the redirect URIs listed for the project for the given client_id

    grant_type

    As defined in the OAuth 2.0 specification, this field’s value must be set to authorization_code

    This endpoint will then return as JSON something that looks like this:

    {
      "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
      "expires_in": 3920,
      "token_type": "Bearer",
      "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly",
      "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
    }

    It is with this information that we can then send through a Bearer header to any of the google apis in order to get information on behalf of the user.

  • GET https://www.googleapis.com/oauth2/v2/userinfo Is the third endpoint, part of Google APIs, this focuses on getting the user’s profile information from Google, it has basic data that is more than enough for us to make a record on our side. A note here: There’s v2 and v3 versions for the endpoint, depending on what version you use for the authentication flow then you should follow the same version for any subsequent endpoint requests. Otherwise you will be denied if you try to request a v3 endpoint with credentials generated from a v2 endpoint. The returned properties will be:

    Property Description

    id

    string An ID on the Google side for the specific user.

    email

    string The email address for the specific user.

    verified_email

    boolean In case the email has gone through a verification process on the Google Side.

    name

    string The string resulting from combining bot the given_name and the family_name.

    given_name

    string The first/given name of the user.

    familiy_name

    string The last/family name of the user.

    picture

    string A URL that takes to the profile picture of the user.

    If you try and use a an expired token, you will not get an error code, however no actual information will be returned.

  • POST https://oauth2.googleapis.com/revoke Is the fourth endpoint from the Google APIs, this receives a request param under token=<TOKEN>. You don’t need to send tokens nor headers for this one. You can send both an access token and a refresh token, if you send an access token it will also revoke its refresh in the background.

    If you try to revoke an expired token, you will be met with an exception.

  • POST https://oauth2.googleapis.com/token will be re-used for refreshing, however the change in behavior will be mandated by the request parameters that are sent to establish the type of flow:

    Parameter Description

    client_id

    The Client ID that we already mentioned that is tied to Google Console.

    client_secret

    The Client Secret that’s also coming from Google Console.

    grant_type

    This should be set to refresh_token

    refresh_token

    The actual refresh token that will be used to validate that this refresh attempt is authentic.

    You are free to request another access token when a previous one has expired or even before that, there are no rules waiting for the previous token to expire. A response from the API should also look something akin to this:

    {
      "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
      "expires_in": 3920,
      "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly",
      "token_type": "Bearer"
    }

In order to get the refresh_token, under the normal flow (following the documentation), only the first authorization from the user will return it. Any subsequent ones will not (even if you send a access_type=offline parameter). Unless you go and revoke access to the application explicitly. However there’s a way to always generate it, and that’s with the extra parameter prompt=consent. This will always ask the user on the Google screen to authorize the app, and on the response side of things will indeed return a refresh token once we try to exchange tokens.

Source: Stack Overflow

Another thing worthy of note is the fact that all the expires_in values are measured in seconds, and it should also be in consequence understood that the moment we get back the token with the expiry_date attached, from that moment in time + n seconds, said token will no longer be valid.

Project Rundown

The project is pretty simple, there are 3 endpoints:

  • /api/generate-url (GET)

  • /api/google-auth-callback (GET)

  • /api/get-tokens (GET)

  • /api/get-user-data (GET)

  • /api/revoke-token (GET)

  • /api/refresh-token (GET)

Postman can be used in order to hit them, however, due to the integration with .http files, even at the IDE level we can hit the endpoints with no issue whatsoever. Just open up any of these files and then click on the Send Request button that pops up.

/api/generate-url

The first endpoint that we should be calling, this constructs the url that can then be opened in a browser to then work with Google’s Auth Website. In the end it will automatically call the /api/google-callback the moment authorization is successful. In can receive two query params that will then be passed down to the google endpoint: accessType and prompt.

/api/google-auth-callback

This is an open endpoint that should be hit by the browser Google Auth website once the flow is completed. It should receive request params with the authentication code (if the flow succeeded). Its behavior is printing the raw query string and then each query parameter separated as key-value pairs for readability.

/api/get-tokens

After we get the authentication code from Google’s side, this endpoint should receive it in order to talk to Google’s API and then get back both a user token and its refresh token. It receives one query parameter: code, this will be passed down to the Google endpoint.

/api/get-user-data

With the Bearer token, we are free to start consuming endpoints from Google, obviously within what was allowed inside of the scope provided at the beginning of the authentication flow. This will expect an Authorization: Bearer <TOKEN> header, said header will be passed down to the respective Google endpoint in order to retrieve the user’s basic information.

/api/revoke-token

Will only receive one request parameter under the form of token=<TOKEN>, it can be used both with an access token and a refresh token, consumes an endpoint from google and passes down the token it picked up from its own request.

/api/refresh-token

Will only receive a refreshToken=<TOKEN> parameter that will be latered passed down to the Google API endpoint in order to request for another access token.

It’s worth noting that after an authorization code has been used for the token exchange it will be invalidated immediately, meaning that the whole flow has to be started over. Also, once a user has accepted the application for the first time, it will no longer ask for consent in the sense of asking for Confirm buttons, just the selection of the account will suffice.

Folder Structure

We are skipping over architecture patterns since this is a prototype (at least for now), we are focusing on quickly trying out the ideas we have in mind, in short, this is a deliverable from a Spike that concerns OAuth’s capabilities in a web app integrated with .NET.

The structure for the solution closely resembles its physical folder structure:

- GoogleOAuthPrototype
|-- src
|   |-- GoogleOAuthPrototype.Application
|   |   |-- Constants
|   |   |-- Files
|   |   |   |-- client_secrets.json
|   |   |-- POCO
|   |   |-- Services
|-- test
|   |-- GoogleOAuthPrototype.Application.UnitTests

The client_secrets.json file is there for illustrative purposes since it will never be comitted to version control. But it should be here as a base line of the source of truth when it comes to connecting to the virtual application that is living at Google Cloud. The code has been adapted so that this file is neccesary to be there (you have to get the file manually through secure means), put it in the path, and that way the prototype will be able to boot up, consume the google credentials and work as intended. This is neccesary to avoid leaking sensitive information.

Besides that, folders are separated by technical concerns (this is not recommended for big projects), but since the prototype is so small, then it’s fine for simplicity’s sake alongside a clear pathway into component visualization when it comes to enumerating all that’s needed in order to integrate with Google Auth.

Cross-Cutting Concerns

Configurations and Security

When it comes to one really important CCC it is the fact that we need credentials from Google in order to hit their endpoints and then on behalf of a user start consuming their APIs.

It is due to this, that we have to establish an implementation that accounts for a secure way to hydrate the app with the data that only lives on a workstation, and is never uploaded to version control.

It was decided that in order to work with this, we have to download the client_secrets.json file, put it inside the Application/Files folder. If the file is for the right application registered in Google Console, then all flows should work correctly.

When putting an app into production we should definitely leverage the IConfiguration interface and its APIs alongside the usage of environmental variables in order to safely hydrate with the right set of credentials at build/deploy times, and not hard-coding it into the project.