Why google auth cannot be without cookies in .NET

Posted by : on

Category : .NET Auth Internals

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

alt_text

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

alt_text

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?

alt_text

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:

alt_text

Answer from Tratcher:

alt_text

alt_text

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.

alt_text

We have 1 private endpoint that is under [Authorize] attribute here.

alt_text

Lets try to access Secret page

alt_text

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

alt_text

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.

alt_text

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.

alt_text

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.

alt_text

Conclusion

So overall it is NOT allowed to use .AddGoogle() without .AddCookies() because

  1. It provides extra overhead of calling OAuth provider each time,
  2. As stated in this article, Remote Auth handlers don’t have .SignInAsync() but each of those handlers calls Context.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.


About Andrii Bui

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