Posts Google Sign In with ASP.NET Core Web API
Post
Cancel

Google Sign In with ASP.NET Core Web API

I couldn’t find a simple, short and straight to the point article about this topic, so I made my own. The complete project is hosted on https://github.com/veglos/dotnet-google-signin-api.

Diagram

/diagram-of-google-sign-in diagram of google sign in

Google provides both the Sign In button and the Google API Client Library for .NET which is recommended for token validation.

Pre-Requirements

We must create a Google Cloud Platform Project and create an OAuth 2.0 Client ID credential https://console.cloud.google.com/apis/credentials. Once the credential has been created, Google will provide us with a Client ID and a Client secret. We will use them later.

Finally, we must declare the Authorized JavaScript origins URIs. For development purposes I have set it up as https://localhost:5001.

Front-end

Not much to say. We just have to be sure to set the proper meta google-signin-client_id, that is, the Client ID we got earlier. The onSignIn() function sends the token_id to the back-end server to be validated. The token contains the Google’s userId. The signOut() will logout from Google, but could also revoke a refresh token. More information at Integrating Google Sign-In into your web app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="google-signin-client_id" content="1234567890-1q2w3e4r5t6y7u8i.apps.googleusercontent.com">
    <title></title>
    <script src="https://apis.google.com/js/platform.js?onload=init" async defer></script>
</head>
<body>
    <div class="g-signin2" data-longtitle="true" data-onsuccess="onSignIn"></div>
    <button onclick="signOut()">Sign Out</button>

    <script type="text/javascript">

        function onLoad() {
            console.log("onLoad()");
            gapi.load('auth2', function () {
                gapi.auth2.init();
            });
        }

        function onSignIn(googleUser) {

            const item = {
                idToken: googleUser.getAuthResponse().id_token
            };

            fetch('https://localhost:5001/api/Access/signin-google', {
                method: 'POST',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(item)
            })
            .then(response => response.json())
            .then(data => console.log(data))
            .catch(error => console.error('Unable to process the request', error));
        }

        function signOut() {
            console.log("signOut()");
            var auth2 = gapi.auth2.getAuthInstance();
            auth2.signOut().then(function () {
                auth2.disconnect();

                // optionally should send the access token to revoke the refresh token too
                fetch('https://localhost:5001/api/Access/signout-google', {
                    method: 'POST',
                    headers: {
                        'Accept': 'application/json',
                        'Content-Type': 'application/json'
                    },
                    body: null
                })
                    .then(response => response.json())
                    .then(data => console.log(data))
                    .catch(error => console.error('Unable to process the request', error));
            });

        }
    </script>
</body>

</html>

Back-end

The SignInGoogle() method validates the token and then should fetch the user’s claims and return an Access Token and a Refresh Token.

According to the docs, the validation process must check the signature, some claims and the optional hd parameter (if it was specified). The Google.Apis.Auth library however, does all of that for us with the exception of the Audience and Hosted Domain claims (the latter not used in this example), which must be provided. Hence the line 24. Again, be sure to use the correct google-signin-client_id.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using Google.Apis.Auth;
using GoogleSignInApi.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;

namespace GoogleSignInApi.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class AccessController : ControllerBase
    {
        private readonly IOptions<AppSettings> _settings;

        public AccessController(IOptions<AppSettings> settings)
        {
            _settings = settings;
        }

        [AllowAnonymous]
        [HttpPost]
        [Route("signin-google")]
        public async Task<string> SignInGoogle(GoogleSignInTokenRequest request)
        {
            try
            {
                var validationSettings = new GoogleJsonWebSignature.ValidationSettings
                {
                    Audience = new string[] { _settings.Value.GoogleSettings.ClientID }
                };

                var token = request.IdToken;
                var payload = await GoogleJsonWebSignature.ValidateAsync(token, validationSettings);

                // Now we are sure the user has authenticated via Google and we can proceed to do anything
                // like fetch the user's claims and provide her/him with an Access Token.
                return "OK";
            }
            catch (InvalidJwtException ex)
            {
                return $"ERROR: InvalidJwt - {ex.Message}";
            }
            catch (Exception ex)
            {
                return $"ERROR: {ex.Message}";
            }
        }

        [AllowAnonymous]
        [HttpPost]
        [Route("signout-google")]
        public async Task<string> SignOutGoogle() //should actually receive a request with the access token and the user Id.
        {
            try
            {
                // Proceed to take any actions if needed such as disabling or deleting the user's refresh token.
                return "OK";
            }
            catch (Exception ex)
            {
                return $"ERROR: {ex.Message}";
            }
        }
    }
}

This post is licensed under CC BY 4.0 by the author.