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:
-
Your application (UI) redirects a browser to a Google URL; the URL includes query parameters that indicate the type of access being requested.
-
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.
-
The application gets an authorization code that we can later exchange for an access token and a refresh token.
-
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
orhttps
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) andoffline
. (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 toconsent
-
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 thegiven_name
and thefamily_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 undertoken=<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 Source: Stack Overflow |
Another thing worthy of note is the fact that all the |
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
andprompt
. - /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 thescope
provided at the beginning of the authentication flow. This will expect anAuthorization: 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.