Implementing Sign-in With Google to your web application.
Introduction
There are tons of things that you can do with Google OAuth Sign-In.
In this article, however, I would like to focus on a fundamental function: using Google OAuth to handle user logins for your website.
Let's begin.
For more detailed information, refer to the official Google Documentation:
Firstly, Register and Enable Google APIs Access for Your Website at the Google API Console.
Google API Console: https://console.developers.google.com/apis/library
Step 1: Create a new project.
Step 2: Setup the OAuth Consent Screen.
Follow the on-screen instructions to fill in your app’s details.
Step 3: Scopes
Click ADD OR REMOVE SCOPES. Here, we will select only one scope:
../auth/userinfo.email
Step 4: Test Users
Add some users for testing purposes.
Step 5: Create Credentials
Navigate to APIs & Services > Credentials > CREATE CREDENTIALS > OAuth client ID:
Select Application type as Web application.
Fill in both Authorized JavaScript origins and Authorized redirect URIs.
These two URLs are crucial. Ensure to use these exact URLs later in your code.
Authorized redirect URIs should be the exact destination URL to which the Google APIs will return values to your website after successfully login.
Step 6: Obtain the Client ID and Client Secret
Once the OAuth client is created, copy the Client ID and Client Secret, as you will need these later in your code.
Build the Website
To begin Google OAuth Login, first prepare the following parameters:
access_type=”online”
(this option can be changed to “offline”, which will be discussed later in the article) client_id= <your Google client id>
– obtained during Google API registration redirect_uri=
the destination redirection page after Google Sign-in – exactly the same as what you registered on the Google API response_type=”code”
scope=”email”
– the allowed scope defined during Google API registration prompt=”consent”
– informs the user about the permissions your application is requesting login_hint
– (optional) If your application knows which user is trying to authenticate, you can use this parameter to provide a hint to the Google Authentication Server. The server uses this hint to simplify the login flow, either by pre-filling the email field in the sign-in form or by selecting the appropriate multi-login session.
Above parameters will be needed to pass to Google OAuth Login Page or API endpoint, which is:
https://accounts.google.com/o/oauth2/v2/auth
There are various ways to initiate Google OAuth Login. One of the simplest methods is to include a link on your login page that redirects users to the Google OAuth Login Page. Attach all the parameters as query string.
Use a HTML <a>
tag (line breaks for documentation purpose):
<a href="https://accounts.google.com/o/oauth2/v2/auth?
access_type=online
&client_id=xxxxx
&redirect_uri=https%3A//mywebsite.com/oauth
&response_type=code
&scope=email
&prompt=consent">Sign In With Google</a>
Or, using JavaScript:
<button type="button" onclick="signInWithGoogle();">Sign In With Google</button>
<script>
function signInWithGoogle() {
const clientId = 'xxxxxxxxxxxxxxxxxxxxxxxx';
const redirectUri = encodeURIComponent('https://mywebsite.com/oauth');
const url = `https://accounts.google.com/o/oauth2/v2/auth?
access_type=online
&client_id=${clientId}
&redirect_uri=${redirectUri}
&response_type=code
&scope=email
&prompt=consent`;
window.location.href = url;
}
</script>
Alternatively, using C# redirection from the backend (line breaks for documentation purpose):
public static void SignInWithGoogle()
{
string clientId = "xxxxxxxxxxxxxxxxxxxxxxxx";
string redirectUri = HttpUtility.UrlEncode("https://mywebsite.com/oauth");
string url = $@"https://accounts.google.com/o/oauth2/v2/auth?
access_type=online
&client_id={clientId}
&redirect_uri={redirectUri}
&response_type=code
&scope=email
&prompt=consent";
HttpContext.Current.Response.Redirect(url, true);
}
Obtaining the Authorization Code
After the user successfully signs in on the Google Login Page, Google will redirect them back to your website along with some parameters (query string
s).
Example of the destination redirect URL will be:
https://mywebsite.com/oauth
or
https://mywebsite.com/login
In ASP.NET WebForms, the physical page ends with a file extension of .aspx and the physical path might look like this:
https://mywebsite.com/oauth.aspx
or
https://mywebsite.com/pages/user/login/google-login.aspx
A routing can be performed. Create or open the Global.asax file and add the URL routing:
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapPageRoute("oauth", "oauth", "~/oauth.aspx");
RouteTable.Routes.MapPageRoute("google-login",
"pages/user/login/google-login", "~/oauth.aspx");
}
I have written an article about routing all pages at once in a single line, here’s the link:
https://adriancs.com/aspnet-webforms/419/automatic-route-all-pages-in-asp-net-webforms/
**Note: Using routing is not necessary for Google API to work; you can still use the absolute file path if you wish. For example:
https://mywebsite.com/login.aspx
https://mywebsite.com/oauth.aspx
https://mywebsite.com/google-login.aspx
The following parameters will be returned together to your website as query strings:
code
= <the authorization code> scope
= <the data that you’re allowed to access from the Google user> authuser
= <the index number of the user logged in on the current browser> prompt = “consent”
<the user is informed about the permissions that your app is requesting>
Example of a full URL (line breaks for documentation purpose):
https://mywebsite.com/oauth?
code=xxxxxxx
&scope=email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid
&authuser=0
&prompt=consent
Obtaining the Authorization Code at the Backend
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (Request.QueryString["code"] != null)
{
string authorizationCode = Request.QueryString["code"] + "";
}
}
}
Obtaining OAuth 2.0 Access Tokens
Prepare the following parameters:
client_id
= your Google client id client_secret
= your Google client secret key code
= the authorization code obtained in the previous step grant_type = “authorization_code”
(fixed string) redirect_uri
= the redirect URL used during Google Sign-in access_type = “online”
(fixed string)
**Note: The access_type has another option value, which is “offline”. This will be discussed in the last part of this article.
These parameters will be sent to Google’s OAuth 2.0 endpoint to obtain the Access Token:
https://oauth2.googleapis.com/token
The parameters can be sent by using either POST
or GET
request.
Example of sending a POST
request:
using System.Net.Http;
public async Task GetAccessTokenAsync()
{
string url = "https://oauth2.googleapis.com/token";
var dicData = new Dictionary<string, string>();
dicData["client_id"] = google_api_client_id;
dicData["client_secret"] = google_api_client_secret;
dicData["code"] = authorization_code;
dicData["grant_type"] = "authorization_code";
dicData["redirect_uri"] = google_api_redirect_url;
dicData["access_type"] = "online";
try
{
using (var client = new HttpClient())
using (var content = new FormUrlEncodedContent(dicData))
{
HttpResponseMessage response = await client.PostAsync(url, content);
string json = await response.Content.ReadAsStringAsync();
}
}
catch (Exception ex)
{
}
}
Example of sending a GET
request (url constructed by using string
interpolation):
public async Task GetAccessTokenAsync()
{
string baseUrl = "https://oauth2.googleapis.com/token";
string encodedRedirectUri = HttpUtility.UrlEncode(google_api_redirect_url);
string urlWithParameters = $"{baseUrl}?client_id={google_api_client_id}&
client_secret={google_api_client_secret}&code={authorizationCode}&
grant_type=authorization_code&redirect_uri={encodedRedirectUri}&access_type=online";
string json = "";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(urlWithParameters);
json = await response.Content.ReadAsStringAsync();
}
}
Example of sending a GET
request (url constructed by using Dictionary
/NameValueCollection
):
var dicData = new Dictionary<string, string>()
{
{ "client_id", google_api_client_id },
{ "client_secret", google_api_client_secret },
{ "code", authorizationCode },
{ "grant_type", "authorization_code" },
{ "redirect_uri", google_api_redirect_url },
{ "access_type", "online" }
};
string baseUrl = "https://oauth2.googleapis.com/token";
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (var pair in dicData)
{
query[pair.Key] = pair.Value;
}
string urlWithParameters = $"{baseUrl}?{query.ToString()}";
string json = "";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(urlWithParameters);
json = await response.Content.ReadAsStringAsync();
}
Google will return the result in JSON.
Example of a successful request:
{
"access_token": "xxxxxxxxxxx",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/userinfo.email openid",
"token_type": "Bearer",
"id_token": "xxxxxxxxxxxxxxxx"
}
Example of an error request:
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
{
"error": "redirect_uri_mismatch",
"error_description": "Bad Request"
}
Building a C# Class Object to Store Response Information:
public class OAuthTokenResponse
{
public string access_token { get; set; }
public int expires_in { get; set; }
public string refresh_token { get; set; }
public string scope { get; set; }
public string token_type { get; set; }
public string id_token { get; set; }
public string error { get; set; }
public string error_description { get; set; }
public bool IsSuccess => string.IsNullOrEmpty(error);
}
Then, use System.Text.Json
to convert the JSON into the class object:
using System.Text.Json;
OAuthTokenResponse tokenResponse = JsonSerializer.Deserialize<OAuthTokenResponse>(json);
string AccessToken = "";
if (tokenResponse.IsSuccess)
{
AccessToken = tokenResponse.access_token;
}
else
{
}
The access token has been obtained. You can use this token to access or save data in the user’s Google Account, such as reading or sending emails, accessing or saving calendar events, getting a list of files in Google Drive, etc.
In this article, we are only interested in obtaining the user’s email address. The access token has a lifetime of one hour, which is sufficient for retrieving the user’s email address from the Google API. In our case, the access token will be used only once.
Next Step: Use the AccessToken to Access Another Google API Endpoint to Obtain the User’s Email.
This action is done using a GET
request.
Example of using a GET
request with Authorization Request Header (recommended, more secure):
using System.Net.Http;
using System.Net.Http.Headers;
public async Task GetEmail()
{
string json = "";
string url = $"https://www.googleapis.com/oauth2/v2/userinfo?fields=email";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", AccessToken);
HttpResponseMessage response = await client.GetAsync(url);
json = await response.Content.ReadAsStringAsync();
}
}
Example of using GET
request with access token sent in query string (less secure):
public async Task GetEmail()
{
string json = "";
string url = $"https://www.googleapis.com/oauth2/v2/userinfo?fields=email&oauth_token={AccessToken}";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(url);
json = await response.Content.ReadAsStringAsync();
}
}
The returning content of JSON:
Example of success request:
{
"email": "somebody@gmail.com"
}
Example of failed request:
{
"error": {
"code": 401,
"message": "Request is missing required authentication credential.
Expected OAuth 2 access token, login cookie or other valid authentication credential.
See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
Combining the two results of JSON, we can create a single C# class Object for handling both situations:
public class ApiEmailResponse
{
public string email { get; set; }
public ApiError error { get; set; }
public bool IsSuccess => error == null;
public class ApiError
{
public int code { get; set; }
public string message { get; set; }
public string status { get; set; }
}
}
Converting the JSON into class object:
ApiResponse emailResponse = JsonSerializer.Deserialize<ApiEmailResponse>(json);
string UserEmail = "";
if (emailResponse.IsSuccess)
{
UserEmail = emailResponse.email;
}
else
{
}
Done!
The user’s email has been successfully obtained at this point.
Before concluding this article, let’s delve a bit more into the ‘Access Token’.
An access token is a digital key that enables applications to access a user’s account or data on a Google service without requiring the user’s password. As mentioned earlier, an access token has a lifespan of one hour. Once expired, your application must repeat the Google Sign-in process. This duration is sufficient if our only concern is obtaining the user’s email. However, for developing third-party applications, like those accessing a user’s Google Calendar or Google Drive, prompting the user for Google Sign-in every hour is impractical.
Remember that when initiating the Google Sign-in, there is a parameter access_type=online
?
If you set the access_type
to offline
, you will get another additional parameter refresh_token
:
{
"access_token": "xxxxxxxxxxx",
"expires_in": 3599,
"refresh_token": "xxxxxxxxxx",
"scope": "https://www.googleapis.com/auth/userinfo.email openid",
"token_type": "Bearer",
"id_token": "xxxxxxxxxxxxxxxx"
}
The value of expires_in
indicates the remaining active time of access_token
. Cache this value, once it is about to expire, or when the access_token
failed to access data from Google, use the refresh_token
to get a new access_token
.
string AccessToken = "";
async void RenewAccessToken()
{
string url = "https://oauth2.googleapis.com/token";
var dicData = new Dictionary<string, string>()
{
{ "client_id", google_api_client_id },
{ "client_secret", google_api_client_secret },
{ "refresh_token", RefreshToken },
{ "grant_type", "refresh_token" }
};
string url = "https://oauth2.googleapis.com/token";
var content = new FormUrlEncodedContent(dicData);
string json = "";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.PostAsync(url, content);
json = await response.Content.ReadAsStringAsync();
var tokenResponse =
JsonSerializer.Deserialize<OAuthTokenResponse>(json, jsonOptions);
AccessToken = tokenResponse.access_token;
}
}
and this is an example of the returning result (JSON):
{
"access_token": "xxxxxxx",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/userinfo.email openid",
"token_type": "Bearer",
"id_token": "xxxxxxxxxxxxxx"
}
There you have it, the new access_token
.
Now, at your website, by using the obtained email address, you may proceed to either create a new user account or log the user in (if the user account has already been created).
In your web application, there might be some methods for creating a session token and implementing auto-login using existing cookies.
When a user first logs into your website, a new login session token is created. This token is saved and then sent to the user’s browser to be stored as a cookie.
Each time the user reopens their browser, the cookie is sent back to the server. The server then retrieves the user information from the database. If the session token from the cookie matches one in the database, an automatic login is performed, and the cookie’s lifetime (expiry date) is extended.
Session cookies typically have an expiry date. If the session token cookie expires, the user is redirected to the Google Sign-in page to restart the authentication process.
There are various methods to manage user login sessions, which could be the topic of another article, perhaps Part 2.
Google OAuth 2.0 sign-in offers extensive customization options. If you have any other ideas, please feel free to share them in the comment section.
Thank you for reading and happy coding.
History
- 19th January, 2024: Initial version