- Why you may want to read this article
- Registering services
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
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
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.
This call just redirects registering to
AddOAuth. Since google provides authentication via OAuth protocol it is expected.
AddRemoteScheme method is just registering auth handler with options. The calls go down to
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
There are 4 layers of Remote Authentication:
Concrete Implementation -> OAuth implementation -> Remote Authentication implementation -> Base implementation
Handlers have different flow and callstack for different actions:
The callstack of
Authenticate method which is called during OAuth communication with Google is:
The callstack of
Authenticate method which is called in protected endpoints with [Authorize] attribute is:
The callstack of
Challenge method is:
RemoteAuthenticationHandler (no method) ->
AuthenticationSchemeOptions contains mostly forward scheme options.
This is needed especially when we use different schemes at the same time like
CookiesScheme. So one scheme can forward to another scheme.
AuthenticationHandler contains some common and initialization behavior. I would like to mention a couple of method here.
There are few virtual methods:
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.
280th line: we are trying to resolve new auth scheme (screen below). If successful - we start challenging from scratch via
288th line: we did not resolve any new scheme - so we execute overridden
HandleChallengeAsync to perform Challenge.
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
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 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.
This guy is responsible for handling response from Remote Auth provider. There are a few important methods.
ShouldHandleRequestAsync returns true if the request is coming to Callback path from Authorizataion Server.
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 is called whenever we got request to endpoint, protected by
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 contains options needed for OAuth / OpenId Connect: client id, client secret, authorize endpoint, token endpoint, usepkce, etc. To be honest nothing special here.
This handler is responsible for all communication according to OAuth / OpenId Connect protocol.
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 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 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 class contains initialization behavior with claims mapping from json.
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 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.
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.