- Why you may want to read this article
- Registering services
- Services
- Conclusion
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
This call just redirects registering to AddOAuth
. Since google provides authentication via OAuth protocol it is expected.
AddOAuth
AddRemoteScheme
AuthenticationBuilder source code
AddRemoteScheme
method is just registering auth handler with options. The calls go down to AddSchemeHelper
.
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.
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
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
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
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.
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.
HandleRequestAsync
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.
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
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
contains options needed for OAuth / OpenId Connect: client id, client secret, authorize endpoint, token endpoint, usepkce, etc. To be honest nothing special here.
OAuthHandler
This handler is responsible for all communication according to OAuth / OpenId Connect protocol.
HandleRemoteAuthenticateAsync
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
117th line: it gets code parameter (Authorization Code)
124-125 lines: tries to exchange code for tokens
137th line: creates empty (default) identity.
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
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
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
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.
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://symptom-diary.com
Pet4Pet: https://pet-4-pet.com