Click here to Skip to main content
15,880,392 members
Articles / Web Development / ASP.NET

Using Google OAuth 2.0 as User Sign-In for ASP.NET in C# - A Basic Overview

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
18 Jan 2024CPOL7 min read 5.2K   96   10   5
Integrate Google OAuth 2.0 with ASP.NET in C# for seamless user authentication
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.

Image 1

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.

Image 2

Step 3: Scopes

Image 3

Click ADD OR REMOVE SCOPES. Here, we will select only one scope:

../auth/userinfo.email

Image 4

Step 4: Test Users

Add some users for testing purposes.

Image 5

Step 5: Create Credentials

Navigate to APIs & Services > Credentials > CREATE CREDENTIALS > OAuth client ID:

Image 6

Select Application type as Web application.

Fill in both Authorized JavaScript origins and Authorized redirect URIs.

Image 7

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.

Image 8

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):

HTML
<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:

HTML
<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):

C#
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 strings).

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:

C#
void Application_Start(object sender, EventArgs e)
{
    RouteTable.Routes.MapPageRoute("oauth", "oauth", "~/oauth.aspx");

    // or

    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

C#
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:

C#
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)
    {
        // error
    }
}

Example of sending a GET request (url constructed by using string interpolation):

C#
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):

C#
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:

JSON
{
  "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:

JSON
// example 1:
{
  "error": "invalid_grant",
  "error_description": "Bad Request"
}

// example 2:
{
  "error": "redirect_uri_mismatch",
  "error_description": "Bad Request"
}

Building a C# Class Object to Store Response Information:

C#
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:

C#
using System.Text.Json;

OAuthTokenResponse tokenResponse = JsonSerializer.Deserialize<OAuthTokenResponse>(json);

string AccessToken = "";

if (tokenResponse.IsSuccess) 
{
    // success
    AccessToken = tokenResponse.access_token;
}
else
{
    // error
}

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):

C#
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())
    {
        // set the access token at the request header
        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):

C#
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:

JSON
{
  "email": "somebody@gmail.com"
}

Example of failed request:

JSON
{
  "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:

C#
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:

C#
ApiResponse emailResponse = JsonSerializer.Deserialize<ApiEmailResponse>(json);

string UserEmail = "";

if (emailResponse.IsSuccess) 
{
    // success
    UserEmail = emailResponse.email;
}
else
{
    // failed
}

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:

JSON
{
  "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.

C#
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):

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Other Other
Programming is an art.

Comments and Discussions

 
QuestionRefresh tokens Pin
Alejandro Gaio24-Jan-24 1:13
Alejandro Gaio24-Jan-24 1:13 
AnswerRe: Refresh tokens Pin
adriancs24-Jan-24 2:44
mvaadriancs24-Jan-24 2:44 
AnswerRe: Refresh tokens Pin
adriancs24-Jan-24 13:55
mvaadriancs24-Jan-24 13:55 
GeneralRe: Refresh tokens Pin
Alejandro Gaio25-Jan-24 10:05
Alejandro Gaio25-Jan-24 10:05 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA19-Jan-24 7:00
professionalȘtefan-Mihai MOGA19-Jan-24 7:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.