- Introduction
- Why .AddGoogle() cannot be without .AddCookies()
- .NET Developers answers
- Demo
- Conclusion
Introduction
In this article, we are going to get answers from the guys developing .NET right now. Since I am working at Microsoft I can just directly ask them inside Teams for what I am really proud of and appreciate.
Every time I need to use Google auth in .NET I encounter unknown spots that are not documented. For example, in docs and tutorials .AddGoogle()
is always used with .AddCookies()
. However Google works through OAuth
protocol with its own cookies
, so I always wondered why we need .NET cookies on top of it at all?
Why .AddGoogle() cannot be without .AddCookies()
RemoteAuthenticationOptions
RemoteAuthenticationOptions source code
RemoteAuthenticationOptions
contains different things for OAuth protocol: HttpClient to communicate to identity provider, data protection provider, callback path, return url, etc.
On top of that it contains SignInScheme here
As you can see from the comments, it is DIFFERENT scheme from Remote Scheme (Google, Github, Facebook) that is used to persist user identity after authentication.
In other words, it saves user identity somewhere on side of your application to NOT trigger OAuth
flow every time. Usually, it is a Cookies
scheme, because once the cookies are set - they carry auth info with each request by the browser.
RemoteAuthenticationOptions contain validate method that ensures that SignInSheme
is not the same as RemoteScheme (current)
here
41nd line: this validation is happening. If our SignInScheme
is the same as sheme
(that is GoogleScheme
) then we are throwing RemoteSignInSchemeCannotBeSelf
).
Possible alternatives
As explained above it turns out that ANY remote scheme including Facebook
,Google
, Github
cannot be used as SignInScheme
.
Google Scheme
uses OAuth protocol
. Under the hood, it performs the whole OAuth process if you are not signed in to Google already (asking for login and password, etc). Then it sets Google Cookies into your browser.
Starting from that point any time a new OAuth process is initiated Google Authorization, the Server will check cookies and return Access/Identity tokens straight away.
It seems like Google cookies are enough for the use case, isn’t it?
1. Verify Access / Id tokens returned from Google
Since inside the handler we have access to Id and Access tokens - we might save them and call some validation endpoint on Google Side, or in case of Google I have heard about this library provided by Google to validate the token.
The problem here is that not all Remote Schemes allow you to validate tokens, so you end up doing some hacks like sending some test requests with this token to Server to check whether you still don’t get 401.
2. Init OAuth process every time
When OAuth flow is happening most of the Authorization Servers save cookies in the browser to not do the Authentication process again (login & password checking). So, the second time you initiate OAuth process - Google Authorization Server will just check cookies and return you Access / Id tokens.
It seems like a relatively inexpensive operation, especially considering most Remote Providers are doing the same way.
For example, try to investigate Github request in the network tab to get your repositories. You will see cookies with the user session going to the request, without the Authorization header. So, why not?
Actual solution
But all workarounds encounter one problem - generalizability
. Different remote authentication providers work in different ways, so the easiest and cheapest way - is to preserve auth info on your application side
.
By doing this:
-
you will trigger OAuth flow only once, and every time your own cookies are expired or revoked - you will initiate OAuth flow one more time. Since the cookies of the Remote Authentication provider could be valid and it still will not prompt the user login and password.
-
you do not care much about different implementations of OAuth (encrypted tokens or not, verifiable tokens or not, etc). You just emit OAuth flow according to protocol, get opaque for your tokens, try to grab claims (user data), and save your own cookies.
.NET Developers answers
Considering my thoughts above - I would like to get some confirmation of my guesses. I would like to know that there is no secret reason that made them use cookies and not emit OAuth every time for example.
I just went to aspnetcore repo and searched through the developers. Then I searched them in my teams and MAGIC - I found them. Not all of them answered me, but a few of these really smart guys did.
This is what I got:
Answer from HaoK:
Answer from Tratcher:
Demo
Demo is run from this repo.
This is simple .NET application with Razor Pages. We have added Google
and Cookies
to demonstrate how they work together here.
We have 1 private endpoint that is under [Authorize] attribute here.
Lets try to access Secret page
Response from request page was 302
with location
header that points to Google authorize endpoint.
As you might know from my previous article about OAuth this is the Authorization Code grant
endpoint, the most secure one.
In your browser you should see the Google Login page
If your browser contains Google cookies already, meaning that you are already logged in somewhere in Google Services - then your Authorization Middleware (.AddGoogle()
) will set your application cookies.
In the screen above you see Authorization Callback to your Return Url
with Authorization Code which will be exchanged to get token.
After we got Access Token, Authorization Middleware will do the request to Google to get user claims and call Context.SignInAsync
with those claims to set Cookies for our Server (localhost:7000).
To understand Google Auth inside .NET - you could read this article.
Our locahost:7000/signin-google
endpoint redirects us to our initial /secret
endpoint where we have Cookies set. With those cookies, we can be Authorized and get User Claims that we got from the Google endpoint.
Conclusion
So overall it is NOT allowed to use .AddGoogle()
without .AddCookies()
because
- It provides extra overhead of calling OAuth provider each time,
- As stated in this article, Remote Auth handlers don’t have
.SignInAsync()
but each of those handlers callsContext.SignInAsync()
here and rely on another Auth Scheme registered for signing principal in. Basically, the code will fail or enter indefinite recursion.
To do so we need to write our own AuthorizationHandler with all supportive classes, and each time the user is not authorized - just emit OAuth flow.
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