.NET Auth internals pt3: Google

Posted by : on

Category : .NET Auth Internals

Why you may want to read this article

This article will cover Google Authentication and Authorization internals in .NET. We will go through the source code as usual, and will understand how it works.

Actually this article covers all Remote Authentication schemes like Microsoft, Github, Facebook, etc, because we will explore some common services that actually do all work.

Tricky questions like “Why we cannot use .AddGoogle() without .AddCookies()” deserve separate articles, because they include answers from developers that develop .NET itself, deeper investigation, etc.

In this article we will explore internals of how Google auth works and what happens under hood every time you use .AddGoogle() in your application.

You might check my article about .NET authentication and authorization internals, to understand the framework and basics.

On top of that you might check my article about OAuth to better understand Google OAuth inside .NET


Registering services

Authentication is similar in .NET so the main magic is happening inside Authentication Handler. But in the case of external authentication some base classes are doing important things, so let’s have a look into the registration of those services.


AddGoogle

GoogleExtensions source code

alt_text

This call just redirects registering to AddOAuth. Since google provides authentication via OAuth protocol it is expected.


AddOAuth

OAuthExtensions source code

alt_text


AddRemoteScheme

AuthenticationBuilder source code

AddRemoteScheme method is just registering auth handler with options. The calls go down to AddSchemeHelper.

alt_text

52nd line: we are validating auth options. This is the answer to why we cannot have GoogleAuth without CookieAuth.

Let’s take a look into those options to check what is going on


Services

There are 4 layers of Remote Authentication:

Concrete Implementation -> OAuth implementation -> Remote Authentication implementation -> Base implementation

Options: GoogleOptions–> OAuthOptions –> RemoteAuthenticationOptions –> AuthenticationSchemeOptions

Handlers: GoogleHandler –> OAuthHandler –> RemoteAuthenticationHandler –> AuthenticationHandler

Handlers have different flow and callstack for different actions:

The callstack of Authenticate method which is called during OAuth communication with Google is:

RemoteAuthenticationHandler.HandleRequestAsync -> OAuthHandler.HandleRemoteAuthenticateAsync -> GoogleHandler.CreateTicketAsync

The callstack of Authenticate method which is called in protected endpoints with [Authorize] attribute is:

AuthenticationHandler.AuthenticateAsync -> RemoteAuthenticationHandler.HandleAuthenticateAsync

The callstack of Challenge method is:

AuthenticationHandler.ChallengeAsync -> RemoteAuthenticationHandler (no method) -> OAuthHandler.HandleChallengeAsync -> GoogleHandler.BuildChallengeUrl


AuthenticationSchemeOptions

AuthenticationSchemeOptions source code

AuthenticationSchemeOptions contains mostly forward scheme options.

alt_text

This is needed especially when we use different schemes at the same time like GoogleScheme and CookiesScheme. So one scheme can forward to another scheme.


AuthenticationHandler

AuthenticationHandler source code

AuthenticationHandler contains some common and initialization behavior. I would like to mention a couple of method here.

There are few virtual methods: HandleChallengeAsync, HandleForbiddenAsync, HandleAuthenticateAsync that will be overridden in the derivatives of this handler.

Basically all methods here are trying to resolve target and use one of those derivatives to execute challenge, forbid or authenticate action.

Below I will show example of ChallengeAsync, but it is similar for authenticate, forbid, etc.


ChallengeAsync

alt_text

280th line: we are trying to resolve new auth scheme (screen below). If successful - we start challenging from scratch via HttpContextExtensions.

288th line: we did not resolve any new scheme - so we execute overridden HandleChallengeAsync to perform Challenge.


ResolveTarget

alt_text

This method is changing one auth scheme to another.

178th line: sets scheme from parameter, but if it is null - then tries to set default scheme from options (from AuthenticationSchemeOptions)

181th line: here we ensure that if the resolved sheme is the same as current one - we return null to prevent StackOverflowException. If it is new scheme - we return new one.


RemoteAuthenticationOptions

RemoteAuthenticationOptions source code

RemoteAuthenticationOptions contains different things for OAuth / OpenId Connect protocols especially redirection part of it: HttpClient to communicate to identity provider, data protection provider, callback path, return url, etc.

On top of that it contains SignInScheme

alt_text

As you can see from comments, it is DIFFERENT scheme from Remote Scheme (Google, Github, Facebook) that is used to persist user identity after authentication.

In other words to save user identity somewhere on side of your application to NOT trigger OAuth flow every time. Usually it is Cookies scheme.

These options contains validate method that ensures that SignInSheme is not the same as RemoteScheme here.

alt_text

41nd line: this validation is happening. If our SignInScheme is the same as sheme (that is GoogleScheme) then we are throwing RemoteSignInSchemeCannotBeSelf. Why .NET team did like that we will answer in separate article.


RemoteAuthenticationHandler

RemoteAuthenticationHandler source code

This guy is responsible for handling response from Remote Auth provider. There are a few important methods.


ShouldHandleRequestAsync

ShouldHandleRequestAsync returns true if the request is coming to Callback path from Authorizataion Server.

alt_text


HandleRequestAsync

alt_text

HandleRequestAsync is called whenever we get redirected from identity provider to Callback Url (OAuth) to process Authorization Code, get tokens, create identity from them, etc.

67th line: ensures wether request is going to Callback Url

77th line: calls HandleRemoteAuthenticateAsync method, overridden in OAuthHandler, that returns authentication result with Authentication Ticket + Principal (user data).

Then a couple of checks are happening with exception handling.

alt_text

162nd line: signs in principal (user data) with SignInScheme. As was previously mentioned SignInScheme is typically Cookies Scheme, not current Scheme (Google, Github, Facebook, etc). On top of that as we previously mentioned SignInScheme must be different from current Authentication Scheme.

170th line: redirects to ReturnUrl (in terms of OAuth) if everything is successful.


HandleAuthenticateAsync

alt_text

HandleAuthenticateAsync is called whenever we got request to endpoint, protected by [Authorize] attribute.

184th line: we try to authenticate with SignInScheme (that is typically Cookies Scheme).

The remaining code is error checking and setting authentication result to set principal (user data).


OAuthOptions

OAuthOptions source code

OAuthOptions contains options needed for OAuth / OpenId Connect: client id, client secret, authorize endpoint, token endpoint, usepkce, etc. To be honest nothing special here.


OAuthHandler

OAuthHandler source code

This handler is responsible for all communication according to OAuth / OpenId Connect protocol.


HandleRemoteAuthenticateAsync

alt_text

HandleRemoteAuthenticateAsync is handling redirection to callback url from Authorization Server. It uses Authorization Code grant, so in this method it tries to exchange Authorization Code for tokens.

59-71 lines: verifying state parameter according to OAuth

74th line: processing errors from authorization server

alt_text

117th line: it gets code parameter (Authorization Code)

124-125 lines: tries to exchange code for tokens

137th line: creates empty (default) identity.

alt_text

139-171 lines: persists tokens from Authorization Server if we mark it in the options.

173-181: creates default ticket with empty principal (user) and return it as auth result


ExchangeCodeAsync

alt_text

ExchangeCodeAsync is used for exchange Authorization Code for Access / Id / Refresh tokens. Under hood it is sending request to Authorization Server with OAuth parameters like client id, client secret.


CreateTicketAsync

alt_text

CreateTicketAsync creates authentication ticket with empty principal (user data) that was created in HandleRemoteAuthenticateAsync.

As you can see this method is virtual and it will be overridden in particular Identity Provider (Google, Facebook, Github, etc).


GoogleOptions

https://github.com/dotnet/aspnetcore/blob/4535ea1263e9a24ca8d37b7266797fe1563b8b12/src/Security/Authentication/Google/src/GoogleOptions.cs

GoogleOptions class contains initialization behavior with claims mapping from json.


GoogleHandler

GoogleHandler overrides CreateTicketAsync and BuildChallengeUrl that are specific for Google Authorization Server. Because as you remember CreateTicketAsync in OAuthHandler creates default identity with default principal that does not contain data.


CreateTicketAsync

CreateTicketAsync is overridden from OAuthHandler, it creates Authentication Ticket with data parsed from tokens.

alt_text

38-41 lines: it sends request to Authorization Server to get user data with Access Token.

47-53 lines: it creates ticket from json response of request performed above.


Conclusion

As we can see, .NET team did a lot of things common for remote authentication. Actual Google Handler and Google Options have implementation of a few methods.

Everything else is just behavior that implements OAuth protocol.

Why we cannot use Google Auth scheme without Cookies Auth scheme will be in the next article. Please subscribe in my social media - there are news and voting.

Please subscribe to my social media to not miss updates.: Instagram, Telegram

I’m talking about life as a Software Engineer at Microsoft.


Besides that, my projects:

Symptoms Diary: https://blog.symptom-diary.com

Pet4Pet: https://pet-4-pet.com


About Andrii Bui

Hi, my name is Andrii. I'm Software Engineer at Microsoft with 5 years of experience.