.NET Core Google OAuth (and other providers) – do I need to store the access tokens?

I’m unclear if I need to store and or use access tokens in this social login implementation.

I can read access token by configuring Startup.cs as below. I know that in many OAuth flows access tokens would be verified before use.

Using .NET Core’s authentication framework however, I’m unclear how much of this work has already been done for me. I’ve looked through the Microsoft.AspNetCore.Authentication.Google source but this hasn’t make it any clearer.

In the LoginCallback action below, I can read an ID for the user using:

claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));

So far as I can tell, this is all I need. I can store that identifier in the database and log the user in again if the ID matches next time they sign in via the authentication provider (Google, Facebook etc).

This is providing that the framework has verified the access token before I read that ID however, and this what I’m not clear on. Is the framework validating the access token before setting the values on the AuthenticateResult so that I can trust it has not been tampered with?

If so, I don’t see any need to store access tokens.

Startup.cs

        services.AddAuthentication(o =>
        {
            o.DefaultScheme = "Application";
            o.DefaultSignInScheme = "External";
        })
        .AddCookie("Application")
        .AddCookie("External")
        .AddGoogle(o =>
        {
            o.ClientId = "xxxxxxxxxxx";
            o.ClientSecret = "xxxxxxxxxxx";


            // Can access tokens by configuring as follows

            o.SaveTokens = true;

            o.Events.OnCreatingTicket = ctx =>
            {
                List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();

                tokens.Add(new AuthenticationToken()
                {
                    Name = "TicketCreated",
                    Value = DateTime.UtcNow.ToString()
                });

                ctx.Properties.StoreTokens(tokens);

                return Task.CompletedTask;
            };
        }) // Other providers ...

Callback handing

    [Route("oauth/google-challenge")]
    public IActionResult GoogleLoginChallenge(string returnUrl)
    {
        return new ChallengeResult(
            GoogleDefaults.AuthenticationScheme,
            new AuthenticationProperties
            {
                RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl, oauthProvider = OAuthProvider.Google })
            });
    }

    [Route("oauth/facebook-challenge")]
    public IActionResult FacebookLoginChallenge(string returnUrl)
    {
        return new ChallengeResult(
            FacebookDefaults.AuthenticationScheme,
            new AuthenticationProperties
            {
                RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl, oauthProvider = OAuthProvider.Facebook })
            });
    }

    [Route("oauth/microsoft-challenge")]
    public IActionResult MicrosoftLoginChallenge(string returnUrl)
    {
        return new ChallengeResult(
            MicrosoftAccountDefaults.AuthenticationScheme,
            new AuthenticationProperties
            {
                RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl, oauthProvider = OAuthProvider.MicrosoftAccount })
            });
    }

    [Route("oauth/login-callback")]
    public async Task<IActionResult> LoginCallback(string returnUrl, OAuthProvider oAuthProvider)
    {
        var authenticateResult = await HttpContext.AuthenticateAsync("External").ConfigureAwait(false);

        if (!authenticateResult.Succeeded)
        {
            return BadRequest();
        }

        var claimsIdentity = new ClaimsIdentity("Application");

        claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
        claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.Email));
        claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.Name));


        foreach (KeyValuePair<string,string> item in authenticateResult.Properties.Items)
        {
            // Read access tokens here
        }

        foreach (KeyValuePair<string,string> item in authenticateResult.Ticket.Properties.Items)
        {
            // or here
        }

        string accessToken = await HttpContext.GetTokenAsync("access_token");

        return Redirect(returnUrl);
    }

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

As I wasn’t clear what actions the framework was taking to verify that the user information was from the authentication provider, I set up requests from the .NET core app to go via Fiddler.

"environmentVariables": {
    ...
    "ALL_PROXY": "http://127.0.0.1:8888"
},

In the case of the Google OAuth provider, following the redirect of the browser back to the signin-google URI, I was able to see the framework make calls back to:

www.googleapis.com/oauth2/v4/token, sending the client ID secret and some other information to obtain an access and ID token.

It then calls to:

www.googleapis.com/oauth2/v4/userinfo passing the access and ID token.

{
  "id": "xxxx",
  "email": "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="621a1a1a1a221a1a1a1a4c010d0f">[email protected]</a>",
  "verified_email": true,
  "name": "Example",
  "given_name": "Example",
  "family_name": "Example",
  "picture": "https://lh3.googleusercontent.com/a-/xxxxxx",
  "locale": "en",
  "hd": "xxxx.com"
}

It is clear that the tokens are short lived, so there is little point in persisting them.

The obtaining and verification of access tokens through use from server to server also looks to be secure.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x