r/dotnet Jul 07 '22 Silver 1 Take My Power 1

Is auth WAY too hard in .NET?

I'm either going to get one or two upvotes here or I'm going to be downvoted into oblivion but I have to know if it's a thing or if "it's just me". I've recently had a fairly humiliating experience on Twitter with one of the ASP.Net team leads when I mistakenly replied to a thread he started about .NET auth. (to be clear I was 100% respectful)

I know "auth is hard" and so it should be but I'm a reasonably seasoned developer with a degree in CS and around 25 years of professional experience. I started my career with C & C++ but I've used and loved .NET since the betas and have worked in some incredibly privileged roles where I've been lucky enough to keep pretty much up to date with all the back/front end developments ever since.

I'm not trying to be a blowhard here, just trying to get my credentials straight when I say there is absolutely no reason for auth to be this hard in .NET.

I know auth is fairly simple in the .NET ecosystem if you stay entirely within in the .NET ecosystem but that isn't really the case for a lot of us. I'm also aware there might be a massive hole in my skills here but it seems that the relatively mundane task of creating a standalone SPA (React/Vue/Angular/Svelte... whatever) (not hosted within a clunky and brittle ASP.Net host app - dotnet new react/angular) which calls a secured ASP.Net API is incredibly hard to achieve and is almost entirely lacking in documentation.

Again, I know this shit is hard but it's so much easier to achieve using express/passport or flask/flask-login.

Lastly - there is an amazingly high probability that I'm absolutely talking out of my arse here and I'll absolutely accept that if someone can give me some coherent documentation on how to achieve the above (basically, secure authentication using a standalone SPA and an ASP.Net API without some horrid storing JWTs in localstorage type hacks).

Also - to be clear, I have pulled this feat off and I realise it is a technically solved problem. My point is that it is WAY harder than it should be and there is almost no coherent guidance from the ASP.Net team on how to achieve this.

/edit: super interesting comments on this and I'm delighted I haven't been downvoted into oblivion and the vast majority of replies are supportive and helpful!

/edit2: Okay guys, I'm clearly about to have my ass handed to me and I'm totally here for it.. https://mobile.twitter.com/davidfowl/status/1545203717036806152

353 Upvotes

186

u/BuriedStPatrick Jul 07 '22

I have spent an ungodly amount of hours trying to get auth code flow to work with a simple OICD server, SPA and .NET API. I have never felt so dumb in my life. I've taken workshops in IdentityServer and read countless blog posts about OAuth and OIDC.

It's been a while so most of it's faded from memory, but I do remember fully grasping the flow itself but simultaneously being completely incapable of implementing it. The terminology is completely alien to me.

The problem is that you don't want to manually implement this security flow. So you use third party libraries that add magic middleware completely obscuring what is happening under the hood making it next to impossible for a regular developer to debug.

What is desperately needed is better ELI5 documentation. Security isn't supposed to be this hard. It's just very difficult to make it easier to understand.

57

u/Jestar342 Jul 07 '22

There is no ELI5 for it, really. It just is that complex.

FWIW I found Auth0's docs the most informative. Here is there article on PKCE for example: https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-proof-key-for-code-exchange-pkce

27

u/BuriedStPatrick Jul 07 '22

Auth0 is seriously a great example of how to make security easier for developers and a real counter-argument to "security is supposed to be hard". I didn't have the option of using it for my particular project due to outside factors. Started out as a PoC with Auth0 but had to pivot to a self hosted OIDC server. That's when things went downhill for me.

15

u/RirinDesuyo Jul 08 '22

Auth0 is seriously a great example of how to make security easier for developers and a real counter-argument to "security is supposed to be hard"

Auth0 and 3rd party idps makes it easy (only 2/3 lines of code) since they're the one taking on the burden of implementing and making sure the hard part is abstracted from you. This is why it's generally not recommended to self-host your own OIDC server if possible as you're fully responsible on implementing all that complexity on your own.

4

u/Trakeen Jul 08 '22

I was really confused on this post since i’ve only done auth using Azure as the IDP and it’s very straight forward. I’d never design my own idp, other companies solved that problem ages ago

3

u/yofuckreddit Jul 08 '22

I've worked with the folks at Auth0. They are the real fucking deal.

15

u/cat_in_the_wall Jul 08 '22

don't be your own identity provider. ever. that's what i say. obviously that's not the way it works for some corporate bs, but integration with an existing identity provider (aad, cognito, gmail, whatever) is soooo easy compared to hosting your own.

any system that makes being an id provider easy is nonsense and is going to be either a security disaster, or will simply never integrate with other systems.

34

u/NooShoes Jul 07 '22

This is my favourite reply so far... Like you, I've spent a lot of time trying to understand the auth flow and I pretty much get it. JWT/Refresh is grand... pretty easy to understand. Securely storing and sending JWTs to an ASP.Net API in a non ASP.Net frontend is a completely different story. A few SO posts and outdated blogs if you're lucky.

The IdentityServer and OpenIddict stuff is incredible, again the API side of things is challenging but with enough time and effort you can figure it out. Again, hooking up a raw (non ASP.Net hosted) SPA into it is super challenging.

What is desperately needed is better ELI5 documentation. Security isn't supposed to be this hard. It's just very difficult to make it easier to understand.

A million times this. Like I said in my OP I'm perfectly willing to accept I'm an idiot but show me the ELI5!!!!

19

u/EternalNY1 Jul 07 '22

Getting Auth right with ASP.Net is a beast indeed, and like you I'm over 20 years into this particular game. :)

I had to implement it with an Angular front-end and .Net 6 on the back-end. You are right, there should be some ELI5 documentation.

9

u/malthuswaswrong Jul 08 '22

Started coding in 1997. Been with .NET since 1.1. Currently on day 3 of trying to figure out auth in an application built in 2014.

→ More replies

3

u/Neophyte- Jul 08 '22

i remember reading a similar post about auth being complex and one of the replies basically said u need a phd on the subject to understand all the nuances.

2

u/malthuswaswrong Jul 08 '22

Since you said you are greenfielding, have you explored Blazor? I work on internal web applications, or internet facing applications with less than 100 visits a week, and Blazor works really well if you aren't trying to making cutting edge UIs. Once I secured an agreement from my boss that we'll "pretty much" only have the controls available through MudBlazor he let us run with it and see what we could do. So far we've been able to do everything we need.

3

u/ashsimmonds Jul 08 '22

I remember one of the OG dotnet gurus trying to show the "basics" of getting auth working in Blazor. Spoiler alert - took him 2 hours and still couldn't figure it out:

Learn C# with CSharpFritz - Blazor Basics with Identity

8

u/[deleted] Jul 08 '22

this was literally me last month, how i implemented it.

Go to controller method, check token, go to auth method, check refresh, go to sign in method, redirect to sign in page, sign in, redirect to controller method, if we have a code, go to access token retrieval method, redirect to controller method once again

→ More replies

4

u/MilkChugg Jul 08 '22

I’m glad to read this and to know that I’m not the only one.

4

u/dreamingsoulful Jul 08 '22

I definitely struggled with the auth code flow in the .Net API. Its good to I'm not the only one to run into issues.

3

u/Rockztar Jul 08 '22

IdentityServer 4 gives some pretty good ways to denug, but their documentation is quite poor.

4

u/Embarrassed_Quit_450 Jul 08 '22

"Simple OIDC server"

There's no such thing.

1

u/jsdppva Jul 08 '22

This. No longer a .net dev but still remember the struggle.

1

u/clitoral_horcrux Jul 08 '22

Likewise, and the out of the box MS example for Auth with a spa is garbage, at least the last time i looked at it a few years ago. It doesn't help that the Auth implementation in asp.net core has had multiple breaking changes and Identity Connect server is now apparently not going to be free anymore so there will likely be another new solution we have to learn all over. Like op, I've been developing in c# for 20+ years and few things have made me as frustrated and feel as noob as dealing with Auth in the past few years. Fortunately I found some good examples other kind souls have put together that helped me get Auth with SPA and silent token refresh working well. I feel like the superb documentation MS used to have for things has really gone downhill. I think it's partly due to the more rapid changes in technology taking place but I also feel that now they just expect you to go through the source code since it's available, which I simply don't have time to do.

→ More replies

1

u/malthuswaswrong Jul 08 '22

magic middleware completely obscuring what is happening

I am currently on day 3 of trying to debug an application I switched from federated identity to Azure. I have all the code done. It's all running, but it's not going to Azure. After a good night's sleep I just had the idea that maybe the csproj, that was created in 2014, may have a flag in there saying to use IIS Express windows auth and bypass all the code I wrote.

I pray that's it because I'm out of fucking ideas.

28

u/QWxx01 Jul 08 '22

Auth is actually quite simple in .NET as long as you steer clear of using ASP.NET Identity. In fact, most of my current implementations don’t even handle auth on the code level but rather sit behind a API management instance that will handle auth for me.

10

u/Rocketsx12 Jul 08 '22

Underrated approach.

6

u/broken-neurons Jul 08 '22

Third party offering IDP?

8

u/QWxx01 Jul 08 '22

For our internal apps, we validate managed identities and users via Azure AD. External customer facing apps are validated against Azure AD B2C. But it could be any Oauth2/OIDC compliant IDP, such as Okta or Auth0.

2

u/TopNFalvors Jul 12 '22

Do you mean a 3rd party API handles it for you?

3

u/QWxx01 Jul 12 '22

No, API management sits in front of your APIs and acts as a reverse proxy. This allow you to apply policies to all incoming requests, such as authentication, throttling, caching and a lot more. This approach also gets you central management and monitoring.

Check out https://azure.microsoft.com/en-us/services/api-management/

→ More replies

36

u/ToeGuitar Jul 07 '22

It's stupidly hard. It's not just you.

55

u/bl4h101bl4h Jul 07 '22

Agreed. Anything that deviates from an out of the box implementation is like trying to push a space hopper through a keyhole.

8

u/NooShoes Jul 07 '22

YES!!! Precisely my point..

3

u/similiarintrests Jul 08 '22

Man i know so little about auth.

I have implemented Microsoft identity? Two lines of code in the startup and off you go. Wish i always could do thst

→ More replies

30

u/zMisir Jul 07 '22 edited Jul 08 '22

I think in .NET world auth frameworks have so much layers that supposed to provide configurability so you can make it fit your own needs but the end result is so absurd that everything works well with each other in default scenario, but for a bit custom use cases you basically end up needing to implement everything from scratch because all the layers depends on each other behaving in default way, making layered monolith that’s both complex and not that scalable. Yeah auth is hard and I’d actually say it sucks on aspnet core. It’s absurdly complex for no additional benefit other than enterprisey look n feel.

Edit: in OP’s case a year ago I had this setup:

  • identity server to issue tokens
  • aspnet identity to manage users
  • BFF framework (in my case our frontend was on next.js, so I went ahead and used a library called next-auth and created a api endpoint for proxying requests with authentication header injected). You simply proxy your api through another api on same domain that uses cookie authentication.

As of now I would rather go with cookie authentication if it’s less painful to host frontend and backend on same host.

14

u/rileyreidsaccountant Jul 08 '22 edited Jul 08 '22 Silver

Most of this isnt .net specific though. I have almost the exact setup, and I understand the pain.

Identity server isn't necessary, you could use an external service. Even if you did need it for something like saml or doing something custom, this isnt part of .net, running your own auth server is difficult and anyone in any language ecosystem would have trouble with it.

.net identity is easy to set up.

Anyone in any language would have to deal with setting up a BFF or front end only pkce flow. Anyone using next js would have trouble, theres nothing .net specific about it.

Thr only net specific thing is authentication and authorization of the token. It's easy enough to authenticate. Making a policy for authorization of the scope is also easy.

Just imagine this scenario in node, what would actually change? Other than creating users, authenticating and authorizing, you're still stuck setting up identity server, a BFF, doing next js auth.

10

u/roughstylez Jul 08 '22

Yeah first comment that gets it IMHO: The difficult part of .NET auth is auth, not .NET.

The difference to other languages is that for "ASP Core auth", you'll find tons of blog articles from the corporate world - a bunch of top results are so corporate, they come from MS themselves. These articles do it correctly, and that is difficult.

BUT OF COURSE ITS EASIER TO DO IT WRONGLY. You'll find some articles for other languages where you will get a login screen in 2 hours, but might contain SQL a la "WHERE username == USERNAME AND password == PASSWORD". This is the hacker way. It gets you to your literal goal "have a login screen", but it's not good enough for serious business.

3

u/niclo98 Jul 08 '22

for "ASP Core auth", you'll find tons of blog articles from the corporate world

You'll find some articles for other languages where you will get a login screen in 2 hours

I get this is popular and above all easy to believe, but it's far from true.

.NET folks like to think their stuff is better than other languages' one simply because most of them haven't look at anything else.

Auth docs from Microsoft you talk about are just bad, if not garbage, and I needed countless hours spent on the same sources that teach "the hacker way" you mention to get anything done.

Most of tech companies do well even without .NET and corporate stuff, better deal with it sooner than later.

3

u/roughstylez Jul 08 '22

Auth docs from Microsoft you talk about are just bad, if not garbage, and I needed countless hours spent on the same sources that teach "the hacker way" you mention to get anything done.

My whole point was that auth DONE WELL is what takes a lot of time, and ASP has the big authority MS behind it which really pushes you into that.

There IS no PHPicrosoft that does this for PHP.

I'm curious though - what are you considering the playing field, that you consider Identity Core docs bad?

API docs, articles, extensive tutorials with working github examples... for language-provided auth handling on that level of configurability and safety... I just wonder who does not only all that - but SO Is much more that this is considered "bad, if not garbage"?

2

u/adolf_twitchcock Jul 09 '22

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-6.0

It doesn't help that MS is pushing their (or duende) platforms in the documentation. This is especially true for API authN/Z. No thank you I don't want to use Azure B2C AD or Duende IdentityServer. The majority of apps don't need OAuth. Using Basic Authentication with HTTP only cookies for a simple SPA is just as safe as OAuth. You should be able to set up a simple but correct authentication for your API in 30 minutes. And maybe you can do that but the documentation doesn't suggest it.

3

u/Type-21 Jul 09 '22

I did exactly this and the stuff I had to reimplement was just absurd. It would have never worked with the msft docs. The key pieces of the puzzle came from stack overflow where people had the same struggle

2

u/zMisir Jul 08 '22

For me .net specific part is there’s a lot of layers and it’s very easy to mess up. And because lots of stuff is just magically happening, debugging and finding out issues is painful, unless you already dealt with same issue ofc. Documentation is not enough, you have to dig source code in order to apply customization or figure out what’s going wrong.

My concern on .net part is that it’s complex (indeed auth is complex, but net implementation has additional complexity), but that complexity have very little added value because you can’t use most of the identity services when you want to go a bit crazy on identity. Well, you don’t usually have to do extra with it anyway, so it’s fine I guess if you want to setup a default signin flow with password and cookies

6

u/davidfowl Jul 08 '22

How is that different from another other abstraction in the framework? Is it because you need to customize it more and therefore you need to understand what is happening?

2

u/zMisir Jul 09 '22 edited Jul 09 '22

First and foremost all the below are just my thoughts and findings I got from working with various stacks.

ASP.NET Authz, Authn and Identity framework have lots of configurability options which only default / most used use cases are documented. For anything non-usual you'll need to dig deep to source code which is not that easy to explore because some stuff was done with "magical layers" instead of direct fn calls which makes API simple but hard to understand what's going on.

For example the other week I was writing a simple AuthenticationHandler to read bearer token, find it on database and attach corresponding user id as a claim to the http context. The implementation was pretty simple, you just pull the token from HttpContext.Request.Headers and match it on database then return result as AuthenticateResult object. I also created a authz policy to check that schema, but I ran into an issue where I would get matching claims attached to ClaimsPrincipal (HttpContext.User) but the policy is failed even tho when I debugged it clearly hit AuthenticateResult.Success factory. After digging dotnet source code I found out that I had to attach schema name to ClaimsIdentity and AuthenticationTicket in order to pass RequireAuthenticatedUser policy rule. I know it's obvious, but only after you've already dealt with this issue, there's a lot of similar cases I've faced with when dealing with dotnet auth* stack (like role based authz using bearer tokens issued by IS4).

When it comes to comparison with other frameworks, some of them are stupid simple and have less features which is probably not a good choice if you really need extendability, but if you don't care about it you can just plug it and it works. By less features I mean they do have extendability support, but you write your own implementation in that case which is sometimes far easier than doing the same on .NET ecosystem because the modules usually are simple and less layered, so you can be somewhat sure that when you call something.success what's going to happen.

If you ask me exact name of the framework, I don't know but I've used a lot of different stacks on various projects from php laravel to nodejs passport and even tried weird ones like next-auth. None of them was as painful as .NET. They were less configurable and worked well for specific use cases. But they worked well for my use cases and I did know that for other use cases there was other solutions or if there wasn't any I wouldn't feel weird to implement it myself.


Well after all I'm still preferring .NET to other ecosystems for building business oriented applications because I've already wasted my time to experiment and learn those edge cases and visit undocumented .NET lands. So I can somewhat deal with those issues, but because I can do this, doesn't mean it's anywhere near being "easy". It's clearly hard and time wasting to deal with. I would actually suggest anyone considering using .NET auth stack to have a mentor to guide them otherwise be ready for days of debugging to find out you need to replace false flag with true on some obscure configuration, or need to replace some hardcoded claim names on some interfaces to get bearer token working.

Edit: above -> below

2

u/davidfowl Jul 09 '22

That’s a good set of feedback. There are layers to the .NET auth stack for sure. The thing I think you’re expressing is trying to extend and fit into the idiomatic is harder than it is in other stacks. That might be true, but I’m lacking examples where that clearly shows up.

Writing your own authentication handler is painful and quite likely less documented than it should be. Hopefully you filed doc issues as you worked through some things (so others could skip those problems).

I’d love to understand if the configuration itself is obscure or if there’s something in the docs that doesn’t connect what you’re looking for, to what’s there in the API.

2

u/roamingcoder Sep 02 '22

As of now I would rather go with cookie authentication if it’s less painful to host frontend and backend on same host.

This is what I do in all of my projects. And if fe and be are on different domains then I add a reverse proxy on the primary that handles the auth. I learned a long time ago that maintainable systems are simple systems.

1

u/bl4h101bl4h Jul 08 '22

This 👆. But it would be manageable with helpful examples for the different scenarios that can be accomplished, rather than having to use several, unconnected resources, trying to piece together the bits you need from each of them.

6

u/BastettCheetah Jul 08 '22 edited Jul 08 '22

Yeah it kills me. The problem for me was the lack of clarity over which setup calls were for client auth, and which were for API auth, when using a combined webapi and website.

All of the tutorials seemed to assume you'd be using razor pages or MVC web + webapi.

Trying to unpick just what is required if you only want API auth and have a standalone front end is gross.

And if you were using a combined approach, debugging which part was wrong was really hard.

Going back to basics and skipping most of the auto configured extensions helped me a lot.

8

u/IoT_Chris Jul 08 '22

It's very hard to assist you here, in general I find auth easier in .Net than other platforms, but that is because I have been doing it for 20 years and I make sure to follow the guidelines for the specific framework version and the deployment topography of each app. Your problem is likely related to the different architectures that we've had over the years and the specific middleware that you are using.
Take a breath, log a job on CodeMentor.io and get someone to look at your code and help you identify where you've gone wrong.

I find the current docs to be very informative, especially if you are using Azure AD, but even if you're not, this guidance for cookies without identity gets you the simplest implementation I can find: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0

IMO in .Net v6 or above, if you are trying too hard, then you are probably doing it wrong. The middleware and configuration is now so minimal, when you get it wrong you are probably mixing in workarounds for previous versions. Debugging this is usually very straight forward, especially for Auth that has a very specific pipeline, you can even add break points in each of the associated message handlers or extend them with your own. Its a very extensible framework.

What is not easy, is for me to understand exactly where you are coming from without looking at your code. I could provide a minimal full stack app with auth in .Net, but I don't think that will work, it would need to be in the same flavour as your existing code base. The problem is in how you are selecting your authoritative resources and how you are applying their knowledge to your situation.

There is no reason to research and re-invent anything here, reach out for help, that is what the community is here for, but use the channels that actually help, venting on reddit is not going to be a commercially productive experience for you.

3

u/green-mind Jul 08 '22

IMO in .Net v6 or above, if you are trying too hard, then you are probably doing it wrong. The middleware and configuration is now so minimal, when you get it wrong you are probably mixing in workarounds for previous versions.

This has been my experience.

I think part of the problem is that the aspnet auth libraries have evolved over the decades and so a quick search may yield results from past iterations. Even if you try to stay within Microsoft official guidance, it's easy to get confused by the many different auth related NuGet packages.

→ More replies

9

u/broken-neurons Jul 08 '22 edited Jul 08 '22

I know this is wishful thinking but part of me has always thought that the problem is that HTTP was designed to be stateless, but there are so many use cases that then add some kind of state to HTTP by tacking it on by various methods over the years, whether that be basic authentication, Windows Authentication, cookies, or local storage, all manner of token variations, or SOAP envelope extensions. Every web tech I’ve touched over the last 25 years, whilst building applications that have some kind of authenticated user has been screaming, “I need a state in a protocol that doesn’t support it by default”.

It’s almost a square peg in a round hole scenario. It’s persistently fighting against the HTTP protocol that wants to be stateless.

Thinking a bit left field for a moment, outside the realms of this conversation really, is there a use case for a “super HTTP stateful protocol” that supports these kinds of paradigms? HTTPSS? SSTP?

I mean, imagine having to code the SSL handshakes and negotiation every every you wanted to make a secure HTTP connection. Thank god we have a protocol for that that we don’t have to worry about doing right (most of the time). I think we need one that is not just secure, but stateful too.

21

u/daigoba66 Jul 07 '22

Kind serious question, why don’t folks just use plain old cookie auth? Even with a SPA, I don’t think you always need the complexity of bearer tokens, JWTs, and OAuth/OIDC protocols.

35

u/rileyreidsaccountant Jul 08 '22 edited Jul 08 '22

If youre in a b2c, sales will want you to add options for common social media logins to make it easier to sign up.

If you're supporting an internal service, there would be some kind of company wide auth used to login into other services, ex. Active directory.

If you're in a b2b, you might want them to be able to access other services from your company with the same login.

Maybe there's a mobile app to support as well.

Cookies auth just doesnt fit the needs of a lot of companies anymore. If it's a simple b2b site and you don't have any other services, no need for social media logins, no mobile app, then go ahead and use it.

8

u/similiarintrests Jul 08 '22

Noob here. Is google/fb(social media login) oath?

10

u/langlo94 Jul 08 '22

Good question, and yes they are oauth providers.

9

u/rileyreidsaccountant Jul 08 '22

Google/fb login uses the oauth standard. Theres more to it than just social media logins though. It's just a standard way of granting access across applications.

5

u/similiarintrests Jul 08 '22

Yeah so if I have to make a site where they want google/Fb login I have to make an Oath solution? Like how much do I have to do to support google/fb login?

Edit. looks like this is it?

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-6.0

4

u/davidfowl Jul 08 '22

We call this “external auth”. You login to the oauth provider and then store the associated information using Cooke auth. It looks just like cookie authentication to the application after the auth dance is done (redirect to the provider, user enters user name and password, data comes back to your site).

3

u/VanillaCandid3466 Jul 08 '22

Oauth2 ... NOT OAuth.

3

u/metaltyphoon Jul 09 '22

OpenID Connect to be specific as OAuth2 only deals with authorization.

1

u/[deleted] Jul 08 '22

[deleted]

3

u/rileyreidsaccountant Jul 08 '22

He used the term plain old cookie auth without oauth/oidc. A BFF doesn't fit that definition.

→ More replies

6

u/Durdys Jul 08 '22

You should use cookies, even if that’s just to wrap the jwt and decode on the backend. There’s a great talk by the IdentityServer dev about this.

→ More replies

5

u/[deleted] Jul 08 '22

You likely don't. OIDC is only used when your application is receiving identity from elsewhere. Even then your application can still use cookie based authentication to manage authorization to your application's resources once you receive the identity from the OP.

7

u/zMisir Jul 07 '22

In my case it was because backend was hosted on different domain than frontend and dealing with third party cookies is just painful.

5

u/daigoba66 Jul 08 '22

That makes sense. But why, if you don’t mind my asking, are they on separate domains? Is that some arbitrary choice, or some other factor?

2

u/Recent-Telephone7742 Jul 08 '22

It’s a pretty common architecture. Remember that a subdomain is considered a different origin wrt the same origin policy that cookies are guided by. If you want them on the same domain you can use path based routing but that gets hairy pretty quick for anything beyond a single client and API system.

4

u/Altosknz Jul 08 '22

Why do you think so? Path routing is pretty easy and quick with reverse proxy

2

u/Recent-Telephone7742 Jul 08 '22

Oh sure. The implementation is not hard at all. But the documentation and usage becomes messy. With a single pair you can tuck the api behind /api and be done with it. What if you have dozens of services?

3

u/Altosknz Jul 08 '22

I have, no issues. Probably depends on implementation, I run kubernetes cluster with traefik as a reverse proxy, so I define for each servise a starting path in deployment configuration, e.g. /alias1/ - service1, /alias2/ - service2...., if starting path not found here, goes to service without starting path, which in my case is frontend service.

2

u/zMisir Jul 08 '22

I used a faas service to host our frontend (vercel) and it required the whole domain to be pointed at vercel, so I can’t apply path routing there.

2

u/adolf_twitchcock Jul 08 '22

What's the simplest way of adding cookie auth + identity to a backend serving a SPA? I get the feeling that it was designed for a razor page app.

3

u/daigoba66 Jul 08 '22

Just call AddCookie(). For reference: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0.

You don’t need MVC or Razor pages, it works fine with API endpoints as long as the cookie is automatically included in every request. This is trivial on a single domain.

The only code you need to write is an endpoint to authenticate (however you want) and sign in the user.

→ More replies

27

u/guyfromfargo Jul 08 '22

I have worked for Auth0 and Okta, and I still get tripped on Auth. Honestly, I think it’s the .Net middleware that makes everything so complicated.

What helped me the most was actually switching over to Python and implementing an auth solution in there. It really opened up my eyes to how at the end of the day you’re just passing around JWT tokens. But C# puts this into a black box, which makes it seem more complicated to than it actually is. If there is any issues with the middleware it becomes a nightmare to debug. Especially when you run into dependency issues.

On top of that Microsoft is so obsessed with active directory. It’s like they can’t fathom you’d write an application that someone besides your employees would login to. So when you’re researching different auth topics in C#, you’ll always fall into some rabbit hole that’s intended for an AD flow vs. a typical SaaS architecture.

But the good news is, once everything is setup and working. The auth is very scalable and just works. It’s getting there that’s the hard part.

5

u/broken-neurons Jul 08 '22 edited Jul 08 '22

This is my bug bear too. Microsoft write code based on their own primary user store focus and potentially other enterprises too (ie. LDAP/ Azure AD), but SaaS, especially federated authentication for SaaS is just ignored. I think my lack of understanding even with many years of experience, is Azure AD stores since I don’t maintain one nor will ever need to.

7

u/davidfowl Jul 08 '22

Isn’t that a complain about using a library vs writing the code by hand? You understand what you write because you wrote it. If you use a library and need to debug it, you have to understand how it works. Why is that easier in python? You can choose to hand roll everything in .NET as well…

→ More replies

5

u/razordreamz Jul 08 '22

Auth is much too hard in .net.

25

u/Alan-Turing-Me-On Jul 07 '22

without some horrid storing JWTs in localstorage type hacks

Other than cookies, this is pretty much your only choice if you're wanting any kind of persistent login mechanism, including opening an app link in a separate tab.

Personally I prefer cookies since you can set them to HttpOnly and Secure to be relatively sure that there's no funny business with them. Just shove the whole jwt in there and call it a day. #justbackendthings Of course, there could perfectly valid reasons for the frontend to access the JWT so you're stuck with local storage nonsense or not setting HttpOnly. Given localstorage doesn't have expiries, I'd probably opt for a slightly less safe cookie (still set Secure though)

If you don't care about forcing a login every time, then just stash them in the application's working memory as a variable. Or if you're using redux, just stash it in there. This is probably the least secure since it could theoretically be accessed by any JavaScript running on the page and not just your JavaScript. If you host your JavaScript off domain and the frontend needs the token for some reason this is probably your only option (but it's been a while since I've really done frontend stuff, so I could be wrong).

12

u/NooShoes Jul 07 '22

Other than cookies, this is pretty much your only choice if you're wanting any kind of persistent login mechanism, including opening an app link in a separate tab.

There is no "other than cookies" though, currently cookies are the only secure way to store and pass JWTs (as far as I'm aware).

If you don't care about forcing a login every time, then just stash them in the application's working memory as a variable. Or if you're using redux, just stash it in there. This is probably the least secure since it could theoretically be accessed by any JavaScript running on the page and not just your JavaScript. If you host your JavaScript off domain and the frontend needs the token for some reason this is probably your only option (but it's been a while since I've really done frontend stuff, so I could be wrong).

Stashing them in Redux is just a proxy for putting them in local storage.

8

u/yad76 Jul 07 '22

Stashing them in Redux doesn't put them in local storage but just memory for that particular page load (unless you specifically write code to persist them to local storage). It's like a worst of both worlds solution where you are exposing sensitive tokens to JS but not even persisting them for any reasonable amount of time.

3

u/Recent-Telephone7742 Jul 08 '22

How do you figure? It’s not a trivial task (if at all possible?) to access an arbitrary variable from a different scope let alone a different module or third party lib.

2

u/Jither Jul 08 '22

It *is* mostly a trivial task. Most implementations - and advisories - think they're safe using e.g. a closure or web worker to make the variable "private", and then completely ignore that if an attacker can get to localStorage through XSS, they can also replace any function in the environment - including many native ones - that the token travels through - right down to the function used to communicate with the web worker. In all but a very few cases (if any), if you have a vulnerability that allows access to localStorage, keeping the token out of localStorage and "guarding" it behind closures and web workers won't help you.

2

u/Recent-Telephone7742 Jul 08 '22

Sure but two things to consider - assuming the site is compromised with a XSS attack (which frankly I think is overblown when using modern FEFs, although there was that stack overflow exploit lol).

A local storage exploit is by definition much simpler to implement because it targets a standard interface. No customization needed - the malicious script can be deployed to any exploited clients. Targeting scoped variables or parsing the ins/outs of core web APIs to extract the token is certainly more involved wouldn’t you say?

If your site is compromised to the degree of replacing core APIs (like fetch) then CSRF is also trivial. Although in the latter at least the attacks need to run through the exploited client rather than capturing the token and utilizing it off site.

→ More replies

2

u/yad76 Jul 08 '22

A Redux store isn't an arbitrary variable though. There are clearly defined ways of getting data out of that store like Provider and connect.

I've never attempted to hack a Redux store or researched how this might work, but, as a potential scenario off the top of my head, it seems like if React is being used and an attacker is able to inject JS into some 3rd party UI component library (or whatever), it wouldn't be difficult to pull values out of a targeted store.

4

u/Alan-Turing-Me-On Jul 07 '22

Agreed that cookies are the most secure way to stash a token. Redux isn't a proxy for localstorage per se (it's worse in my opinion) but I'm not gonna quibble over that. What you end up chosing as your storage mechanism needs to picked based on the access patterns that use the token. If the frontend app never needs to read the token, then cookies are your bestest bet. On your auth flow, just have the server return a timestamp of when a refresh endpoint needs to be called and the refresh endpoint also returns the same kind of timestamp.

If the frontend app does need to read the token for whatever reason, then you need to start looking at the less secure options, which is why I included them and why you'd want to use them. Otherwise I'm just some asshole screaming cookies with nothing to back that up.

1

u/nense0 Jul 08 '22

But why do you think it is a security risk to have the jwt in local storage? Xss in modern frameworks seems almost impossible. You need to do something really bad to be vulnerable.

1

u/Cjimenez-ber Jul 08 '22

There is a browser event that runs before leaving the page. An alternative to sorta hack persistency is to use that to put it in localstorage and then on the first event of the SPA to put it back into memory (redux, Zustand, etc) and then remove it from localstorage or cookies.

Its definitely not perfect, but it's better than logging people out on refresh.

Or go through the pain of setting OpenId Connect Code Flow with PKCE.

5

u/Alan-Turing-Me-On Jul 08 '22

Just put it in local storage in the first place. Or just use a cookie. The in memory solution is really the worst and least secure option and I've only included it for completeness.

3

u/Recent-Telephone7742 Jul 08 '22

Can you explain the security concerns for in-memory? Local storage is available to any script running on the page. A variable in memory is block and module scoped isn’t it?

→ More replies

11

u/ShodoDeka Jul 07 '22

I mean, like with every other language out there it depends on the framework you use for auth.

Obviously if you want to use an auth backend that never bothered to add support for .net then it will be an uphill battle. Just like any other language/framework/auth backend combination that wasn’t build for each other.

7

u/NooShoes Jul 07 '22

I want to use ASP.Net for auth. The framework has an incredible AuthN/AuthZ story... best in class without a doubt!

I would also like to be able to authn(z) non ASP.Net hosted applications into this framework and I think the guidance on this is seriously lacking.

3

u/rbobby Jul 08 '22

Very similar boat... and I too found auth in mvc core to have been seriously mysterious.

I tried to write you a simple explanation... and it's like a mud pit. The variety of ways doing authentication is pretty broad.

For an SPA + API you can definitely use OAuth and pay for AzureADB2C or Auth0.com to handle the auth parts. You'll have to configure their services and use something like msal.js or auth0's js library for the client side stuff. Server side is a lot easier, just use OIDC and a touch of config.

If you want to have your own database of users and passwords you can use "individual accounts" the built in identity stuff with mvc core. It's not OAuth2.0, it regular classic authentication cookies. Not a bad choice, probably up to 1,000 users (maybe). All the builtin pages (login, forgot password, etc) are classic web pages and not SPA. This might make it tricky to do the transition between the identity pages and the SPA smoothly. If it were me I'd be most worried about what happens with the auth cookie expires and an Ajax request fails.... how does the user get redirected to login? Will it be ok for them to suddenly lose their work? Maybe a client side timer that forces them to a "session timed" page would help.

Trying to use your own database of users and passwords setup to work as an API (i.e. no login page, a login api end point) would be time consuming and probably rife with security holes. Better to just use AzureADB2C or auth0.

If you've got specific questions I'm happy to at least listen to them :)

→ More replies

3

u/dotnet_or_notdet Jul 08 '22

Simple login is fairly straightforward using Microsoft Identity, for OIDC I've found that OpenIddict is pretty good, now that IdentityServer is going to be sunset in favour of a paid solution called Duende.

3

u/jaredthirsk Jul 09 '22

OpenIddict is pretty good

An easy way to try a pre-packaged OpenIddict binary is OrchardCore CMS with its OIDC module: https://docs.orchardcore.net/en/dev/docs/reference/modules/OpenId/

4

u/Lothy_ Jul 10 '22

I don't think it's too hard, but I think the documentation (at least historically, but also perhaps today) places emphasis on doing it with EF Core while neglecting a 'vanilla' approach.

I also think a lot of people make it harder than it ought to be by preferring non-cookie authentication.

And finally, I think the architectural decision to use subdomains is not merited for 80% or 90% of applications. This necessitates CORS, which in turn undermines the simplicity of cookie-based authentication.

If you operate on a single domain then life is much easier, and you aren't 'fighting' with the browser all of the time.

14

u/Unexpectedpicard Jul 07 '22

What in your opinion was hard about it? It can certainly be hard if you're talking about oauth and external providers but your standard JWT auth system is not complicated. You can have basic JWT auth in place in a few lines of code.

10

u/NooShoes Jul 07 '22

I agree, it's not that hard.... but getting a JWT auth system with proper refresh where the tokens are not stored in localstorage seems to me to be way harder to achieve in ASP.Net core than in other frameworks. As I said in my OP, I may be completely missing something here but I still haven't found a coherent doc on achieving this.

11

u/Jestar342 Jul 07 '22

ASP.NET has no localstorage concern. ASP.NET isn't client side.

I've made countless apps (web and mobile) and server to server that use OAuth/OpenId with .NET backend. What's your actual problem, maybe I can help?

2

u/NooShoes Jul 07 '22

Ah yeah - I get that the localstorage concern is not an ASP.Net thing. Personally I don't have an actual problem as I think I've figured it out (although I'm not sure I'm 100% rock solid best practice on it).

Basically - given an ASP.Net API with Identity & Auth all setup and working perfectly (which is reasonably easy and very well documented) - how would you go about hooking up a React/Angular/Vue frontened into this without wrapping it in a clunky ASP.Net host app?

9

u/Jestar342 Jul 07 '22 edited Jul 07 '22

OK well the problem is likely because there is a purpose/understanding mismatch. ASP.NET Identity is a membership store, and supports a bespoke cookie token not unlike ASP SESSIONID stuff from yonder. The token generation and so on is handled by and OpenId/OAuth service like IdentityServer or Openiddict, using "Identity" for the user authentication and store.

This page has the info you need, though as per with MS docs it will take some experimentation and messing around to really understand. https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-6.0

If you're trying to do it without the IdentityServer bit, that's your problem - "Identity" just isn't cut for that task.

4

u/NooShoes Jul 07 '22

I'm glad you posted that doc as it absolutely gets to the root of what I'm talking about here. The SPAs in those examples require being wrapped in an ASP.Net host which sets up the auth and passes the cookies from the SPA to the API.

6

u/Jestar342 Jul 07 '22

... that's just to serve the html to your browser.

Everything React is in the ClientApp folder.

2

u/NooShoes Jul 07 '22

Everything apart from what we're talking about here.

Have you tried moving the React app out of the ClientApp folder and getting it to work?

3

u/Jestar342 Jul 07 '22

I'm not sure I understand your point anymore. Of course I have. I have built apps that are deployed to various platforms that connect to dotnet APIs across various other platforms using OAuth. E.g., Netlify apps that connect to Azure deployed APIs, or AWS Amplify to AWS API Gateway, Vercel to AWS Fargate APIs and so on.

3

u/Rocketsx12 Jul 07 '22

I read that doc and apart from using the ASP.NET bootstrapped react/angular to serve the app for example purposes it's not clear which part of the auth setup you think requires it.

3

u/NooShoes Jul 07 '22

Ooooooooookay.... this is where I might be failing to understand things and about to embarrass myself but I'm going to take the hit for the benefit of others who might stumble on this thread trying to figure this out.

My understanding of that doc and when you follow the instructions is that your SPA gets wrapped in an ASP.Net app which passes the cookies and tokens to the SPA. I've just spun up a "dotnet new react" app and the Program.cs which wraps it has

builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = "cookie";
        options.DefaultChallengeScheme = "oidc";
        options.DefaultSignOutScheme = "oidc";
    })
    .AddCookie("cookie", options =>
    {
        options.Cookie.Name = "__Host-bff";
        options.Cookie.SameSite = SameSiteMode.Strict;
    })
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = "https://demo.duendesoftware.com";
        options.ClientId = "interactive.confidential";
        options.ClientSecret = "secret";
        options.ResponseType = "code";
        options.ResponseMode = "query";

Which I understand is necessary for passing the tokens/cookies to the underlying SPA? If not, I'd be love to see how to manage this in a SPA not wrapped with the ASP.Net app?

4

u/Jestar342 Jul 07 '22 edited Jul 07 '22

AddAuthentication is telling ASP.NET where to find the authenication info (and what scheme)

AddCookie is telling ASP.NET to set a same-site policy of "strict" on a cookie named "__Host-bff", which is used by the MVC/Razor views - not react.

AddOpenIdConnect is setting up the endpoints and details for token generation and exchange. You should see (I think, from memory, that it endpoint discovery is enabled by default) a list of endpoints on '/.well-known/openid-configuration'

I agree the example could be more explicit with separation of client and server projects.

3

u/NooShoes Jul 07 '22

Yeah - again, I totally get that. I absolutely understand what all that is doing...

Now, show me an example where the SPA is completely decoupled from ASP.Net?

→ More replies

5

u/Unexpectedpicard Jul 07 '22

What you're talking about isn't a backend concern is it? The frontend handles storing the token and refresh etc. That's not a .net issue.

2

u/NooShoes Jul 07 '22

No, it's absolutely not a backend concern. AuthN(Z) in ASP.Net is superb and really well implemented and documented. My gripe is that it's seriously hard to hook up non ASP.Net properties into this and the only guidance for this I can find is either storing JWTs in localstorage or hosting your SPA in a clunky ASP.Net wrapper host.

3

u/PoisnFang Jul 08 '22

I agree, I have spend sooooo much time on Auth in .NET. However, I will say that I am now using MSAL with Sveltekit and it is coming along nicely.

3

u/JamesAllgood Jul 08 '22

I feel you. I am in my fifth year as a dotnet developer, and right now I am supposed to connect an Angular SPA to a dotnet 6 api via OIDC. I have never done this before, and it is really hard for me to find anything remotely useful.

Reading what you guys are saying is kinda depressing for me. Some of you have been working in this field for decades and are still having trouble, how am I supposed to do this within a reasonable amount of effort? Meanwhile my manager complains about the bad volacity or whatever…

5

u/SolarSalsa Jul 08 '22
  1. ASP.NET API usually sends an extra Authorization header for an authenticated / logged in users such as: Authorization: Bearer <some base64 encoded jwt token>
  2. Then on your non ASP.NET frontend (Read/Vue/Angular/etc.) you read that header and save the jwt token in your javascript.
  3. Then when you make an API request from your frontend (React/Vue/Angular/etc.) you add an extra Authoriztation header with the same jwt token.

And boom you're done.

It's confusing because its not the responsibility of ASP.NET API and thus wont be included in the documentation or tutorials.

3

u/NooShoes Jul 08 '22

Yes - but this flow requires you storing your bearer token in your browser's local storage so you can add it to your API request. This is simple enough to achieve but there's a massively downvoted post on this thread already where this was suggested.

You can see in my OP where I said I didn't want to store the JWT in localstorage, if you have another suggestion on how this can be achieved I'd love to read it but I think that the only secure way of managing this currently is using HTTP only cookies and it's unclear to me how to manage this with a standalone SPA and an ASP.Net API.

5

u/tritiy Jul 08 '22

As far as i know you store your JWT token in memory and refresh token is stored as a cookie. When you reload your page you go to refresh url where your refresh token is automatically passed as a cookie. From refresh url you get fresh jwt to use as bearer. This avoids CSRF (token is not auto-sent) and storing token in local storage (potentially mitigating XSS) while allowing user to remain logged in across browser sessions.

3

u/rebornfenix Jul 08 '22

dont store the JWT. They are super lightweight to just call the auth service on page load and toss it in a global js variable (Redux makes it stupid easy).

2

u/SolarSalsa Jul 08 '22

While localStorage is an option it is not a necessity. You might be creating your own hurdles and making this more difficult than it should be.

→ More replies

17

u/0bcd Jul 07 '22

Creating a REST API with proper auth is so absurdly hard in .Net while it takes a few minutes with Django REST Framework.

It's disappointing because I like everything else about ASP.NET Core and C#. API auth is .Net's achilles heel.

10

u/NooShoes Jul 07 '22

Thank you!

I've tried to have this conversation on a couple of occasions when prominent ASP.Net devs have tweeted "why do people find auth so hard in ASP.Net" (one of which was actually in response to another reddit thread on this) only to be wellacksully'd so hard I started to question my own worth.

7

u/captain_arroganto Jul 08 '22

God Thank you !

I seriously developed an inferiority complex when dealing with Auth on .net core.

I mean, rest of the app was a breeze.

I seriously felt perhaps I am not that all cut out to be an indie web dev.

7

u/rebornfenix Jul 08 '22

.net core web api using authorization attributes, and the auth pipeline takes all of 10 lines of code.

You add attributes to your controllers / controller methods with the authorization policy, then in the configure services call .AddAuthentication() and go.

If you want to start talking about generating JWT tokens and handling the refresh of things, that gets complicated not because of .net but because auth is hard.

4

u/davidfowl Jul 07 '22

Do you have a sample of auth with a Django REST API?

2

u/[deleted] Jul 08 '22

But it isn't hard. It's very simple to configure JWT Bearer token middleware in .NET.

4

u/[deleted] Jul 08 '22

[deleted]

4

u/davidfowl Jul 08 '22

What does this look like in laravel? Do you have an example?

1

u/[deleted] Jul 08 '22

[deleted]

4

u/davidfowl Jul 08 '22 edited Jul 08 '22

Like this https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers

What I’m looking for is a sample. I think you might be right but it’s hard to know if it’s the same auth scenario as being described above.

1

u/[deleted] Jul 08 '22

[deleted]

3

u/davidfowl Jul 08 '22

Yea this is what our default template with authentication does. But I’m pretty confused as to what this is doing. Is this using cookies for the authentication protocol?

I don’t see tokens or anything like that, I’m assuming cookies? If yes, is there a node frontend proxying requests to a laravel backend?

2

u/NooShoes Jul 08 '22

Honestly, I'm leaning this way too. I'm annoyed about this just now as I'm currently prototyping a greenfield app and I figured I'd first get auth out of the way.

I've been keeping my eye on Laravel for a few years now and I've always been a sneaky fan of PHP since writing a few WP extensions many years ago.

2

u/[deleted] Jul 08 '22

[deleted]

2

u/davidfowl Jul 08 '22

Can you provide an example of that where laravel is used as an API without serving the content for the SPA? Is that turn key in laravel?

4

u/rebornfenix Jul 08 '22

Identify your threat vectors. Everything we do in application development is about tradeoffs. The more secure a site is the harder it is for users to use. Storing the token in local storage makes the user experience from closing the browser much nicer but its "less secure" than other methods. But if the app has no users, who cares how secure it is.

Authorization is easy to get put in place in a generally secure way. The examples may be a bit lacking but using OIDC / OAUTH 2 is really simple and has lots of good examples. React SPA with OIDC will get you lots of examples for the client side and ASP.NET core OIDC will also get you lots of examples for the server side.

If you need to stand up your own Identity Provider, that gets complicated as hell but when you need that, instead of a third party like Azure B2C or AWS Cognito, both of which are free for up to 500k MAU, then your app is probably in a spot to be highly profitable, and you can afford expert contractors to come in and do it.

1

u/davidfowl Jul 08 '22

This is solid advice.

3

u/Aquaritek Jul 08 '22 edited Jul 08 '22

Yeah, I concur sir.

I'm a tenured .Net dev having worked on all sorts of projects of many shapes and sizes. Usually though, I've gotten involved after the project has already been in development for awhile.

Well about 3yrs ago I spun off to do my own thing. Found a real estate company that wanted to build something of an ERP/Prop/Fin/Proc management system for internal use. I signed on and got real fired up about the desired scale of the app so I decided to go microservices architecture and ultimately went down that rabbit hole.

Long story short, I opted to go with IdentityServer4 to handle the various auth schema requirements. It took me nearly 30 days to even get a rudimentary understanding of it with it's egregiously assumptive documentation (only if you worked on the project would you understand what they were referencing half the time). I learned more about it from terrible blog posts and free YouTube content honestly.

Then another 30 days or so to actually get it working in a robust multi instance setting for server to server auth, client to server, sso between client services.. etc. I clearly remember though at several points just making stuff up because things weren't working or the documentation was just completely missing for my situation - even stack was empty of advice lol.

When it was all said and done I spent another week just documenting how the feck it actually all connected and worked so that someone else would even have a thimble of a chance coming into the project. More specifically directing to not touch a damn thing because it felt fragile enough that if Bill had the sniffles on Tuesday none of our users were going to be able to login for some reason.

It definitely was one of the most headache inducing situations I've been through in my career.

With peace, Aqua.

2

u/nirataro Jul 08 '22

There are good alternative such as https://fusionauth.io/ and https://www.keycloak.org/

2

u/chucker23n Jul 08 '22

In terms of documentation and project templates, I think there's too little in the way of "what if you don't want to use Azure". Beyond that, I'm not sure there's anything .NET makes particularly hard.

You inherit from AuthenticationHandler<T> in your ASP.NET Web API to handle the actual authn (e.g., "where are user accounts actually stored"). You can then, for example, put a JWT in a HttpOnly cookie. Done.

So I think there could be a bit more hand-holding, but I don't think .NET goes out of its way to make it hard.

2

u/fori920 Jul 08 '22

Reminder: this is not entitled nor exclusive to .NET.

Now, yes, auth is difficult. That’s why I always plan to do it in the middle of development and piece by piece because it’s tough to customize and make sure to want to keep your users feel secure (i.e: no breaches)

2

u/broken-neurons Jul 08 '22

Like you I’ve been around Microsoft tech for a long time going to to classic ASP in the late 90’s and moved with the times as we’ve all moved moved from Framework and WebForms and MVC to Core to NET6.

Auth is definitely challenging once you move outside the “I’ve built a web application for my company and we have our users in Azure AD and click, click done”, or “I’ve built a B2C ASP.NET website and I want to enable a Google and Facebook login”, or “I’ve built a B2C ASP.NET hosted SPA, and I want to use ASP.NET Identity”. Anything outside those confines and it gets hard.

B2B SaaS for example is hugely challenging because you start dealing with federation and multi-tenancy. Is the tenant a JWT claim, or a custom extension. I’ve never figured that out from the spec.

One option for pure non-ASP.NET hosted SPA’s are Azure Static Web Apps which supports OIDC, but how it actually deals with the token / cookie appears on first glance to be a black box.

https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=openid-connect#configure-a-custom-identity-provider

There are a load of moving parts in the auth requirements and it’s a steep learning curve, even for seniors with lots of experience in the basics of HTTP, session and cookies, which requires a lot of hand-rolled code in the old days, versus Membership and later identity. I’ve seen these discussion before every time the goalposts moved.

What I have noticed is the further we abstract over the top of HTTP, the less likely it becomes for new developers to really look at what is going on between client and server in terms of requests and responses. The other day I was asked to help a mid level developer with an issue with his Razor page models, and came to the realization that he genuinely didn’t understand how more complex HTML forms posted data to the server (I.e. lists or radio button groups). I encouraged him to install fiddler and helped him configure it to be able to watch each request and response so he could begin to understand that his code and the Microsoft libraries he was using were just abstractions built upon that basic premise (and of course HTTP itself just being another abstraction layer, and so on).

What is missing is best practice. I often watch Dominik Bayer conference videos and someone like him or Brock Allen have a wealth of knowledge that they have built up whilst working hard in this space for years.

The ideal would be some kind of questionnaire which defines what you are trying to build, how you want to build it, what your teams key skills are, and who your audience is / are. You then have the suggestions that fit and the samples that show you how to do it properly.

2

u/guillaumechervet Jul 08 '22

You may read this article that explain a litle about oauth2 and openid connect. I think it is a good introduction. https://medium.com/just-tech-it-now/increase-the-security-and-simplicity-of-your-information-system-with-openid-connect-fa8c26b99d6d

2

u/Apprehensive-Monk952 Jul 09 '22

Because membership management is hard and it's also hard to customize.

I have worked with Django as well and although it seems simple on the surface it's extremely hard to customize it, even use JWT.

2

u/matthewblott Jul 09 '22

I completely agree. I put this together for that reason ...

https://github.com/matthewblott/simple_aspnet_auth

2

u/ivanmont Jul 13 '22

Well, I happen to be involved in a code generator (https://github.com/jhipster/jhipster-dotnetcore).

The authorization part is quite complex so I prefer to start with something that already works. When you generate the application you choose between JWT/Oauth. For oauth we use okta.

The generator allows creating a dotnet backend. And also a frontend (angular, react, vue or blazor). All that with entities and relationships. It is great for MVPs. It is not perfect buth might help starting a project.

→ More replies

6

u/[deleted] Jul 08 '22 edited Jul 08 '22

This post gets made every other week. So, auth is hard. It is the most important part of any application. I disagree with the sentiment that it is harder than it should be. If it is hard to manage it is a signal the approach being taken is wrong headed. You can spin up a full fledged identity solution using cookie based authn in minutes in .NET. Need social login, depending on the provider it's a matter of configuration. OIDC? Matter of configuration. MFA? It's a matter of configuration. Need to be an IDP? How about you don't do that if you can't manage the current auth APIs. Have any already existing system that needs to be ported to MS Identity? It's going to take some work, just like any other auth system.

But, here is the real question why does your SPA require any knowledge about tokens? SPAs cannot securely manage them, just like they can't securely manage secrets. So don't expose tokens or secrets to them. Rely on your backend to handle tokens and token requests. Your backend can rely on cookie based authentication for the SPA and then proxies requests on behalf of the SPA using whatever tokens are required to whatever internal or external API as required. From there you can implement things to make it so that your SPA can take advantage of the security features found in .NET that protect against XSRF.

2

u/jaredthirsk Jul 09 '22

So don't expose tokens or secrets to them. Rely on your backend to handle tokens and token requests. Your backend can rely on cookie based authentication for the SPA

My understanding of the OP is that he is looking for documentation for this.

example.com - spa (not hosted by .NET), using opaque cookie to talk to backend

example.com/api - ASP.NET Core backend

Where is a good example of this?

2

u/Willinton06 Jul 07 '22

I think I’m missing something cause it takes me a few minutes to set up JWT auth on ASP

3

u/rileyreidsaccountant Jul 07 '22

Auth is complex because there's so many options and different ways to do it. I'm ignoring actually configuring your own auth service like identityserver, because that's difficult on its own.

Just using a 3rd party auth service is hard, especially if you're using a javascript framework for the frontend.

It's tough to find resources to do exactly what you want.

I took forever to find out how to secure a web API using identityserver, while having a next js app as the client. Theres different kinds of auth flows, different jwts, cookies. You can make a BFF for the frontend or a frontend only flow. Then to add even more complexity to it, you have to handle auth two ways in next js, on the front end server and then during client side.

5

u/NooShoes Jul 07 '22

Yeah - we all know this. My point is that there should be more coherent guidance from the ASP.Net guys on this.

1

u/rileyreidsaccountant Jul 07 '22 edited Jul 07 '22

I dont think we could expect them to go through all of that indepth though especially since things change pretty frequently.

Their simple case scenarios, like using razor pages with the .net identity packages are pretty much all theyre capable of. You still have to learn things like oidc and oauth yourself, and the 3rd party providers all have reasonable documentation for .net. Theres also sample repositories and templates that can fill in the gaps.

The only thing I would like better documentation on is some of their packages like the jwtbearer one.

Also this isnt a .net specific issue, it's not like any other framework has better documentation that you can point to as being much better.

1

u/xortar Jul 08 '22

Auth is a language agnostic problem with many existing language agnostic solutions.

I’m admittedly not very familiar with existing .Net auth implementations, but I’m not that surprised by the number of replies venting frustrations with them.

However, if existing implementations do not meet your own requirements, why not implement a solution yourself? For instance, OAuth2 is well-defined and comparatively easy to implement.

I understand why it is desirable to use off-the-shelf solutions for many of the ancillary concerns of our applications, but when no such viable solution exists, I do not get the common aversion to implementing it oneself.

If a decent driver for my target database does not yet exist in my chosen language, I implement it. If an IAM solution does not yet exist that matches my requirements, I implement it.

I get the desire to complain about the ergonomics of a solution, but I often find it more productive to contribute to a better solution.

Hopefully I did not misinterpret the intended nature of the OP. I have a tendency to make improper inferences and produce tangential ramblings.

0

u/Catalyzm Jul 08 '22

Gods help you if you don't use EF.

2

u/broken-neurons Jul 08 '22

You were getting downvoted but I agree with you. There are situations where you don’t use EF and you don’t use EF migrations. Many of our migrations are done as database first and managed in Roundhouse, DbUp or FluentMigrator. We have way too many data scripts and fine tuning of the database schema to use EF Core migrations.

2

u/davidfowl Jul 08 '22

I assume you want to use identity but you don’t want to use EF? You can: - Write a custom store - Don’t use identity but keep using the authentication system

1

u/Catalyzm Jul 08 '22

It's simple you just... ;-)

Interfaces to implement when customizing user store
IUserStore
The IUserStore<TUser> interface is the only interface you must implement in the user store. It defines methods for creating, updating, deleting, and retrieving users.
IUserClaimStore
The IUserClaimStore<TUser> interface defines the methods you implement to enable user claims. It contains methods for adding, removing and retrieving user claims.
IUserLoginStore
The IUserLoginStore<TUser> defines the methods you implement to enable external authentication providers. It contains methods for adding, removing and retrieving user logins, and a method for retrieving a user based on the login information.
IUserRoleStore
The IUserRoleStore<TUser> interface defines the methods you implement to map a user to a role. It contains methods to add, remove, and retrieve a user's roles, and a method to check if a user is assigned to a role.
IUserPasswordStore
The IUserPasswordStore<TUser> interface defines the methods you implement to persist hashed passwords. It contains methods for getting and setting the hashed password, and a method that indicates whether the user has set a password.
IUserSecurityStampStore
The IUserSecurityStampStore<TUser> interface defines the methods you implement to use a security stamp for indicating whether the user's account information has changed. This stamp is updated when a user changes the password, or adds or removes logins. It contains methods for getting and setting the security stamp.
IUserTwoFactorStore
The IUserTwoFactorStore<TUser> interface defines the methods you implement to support two factor authentication. It contains methods for getting and setting whether two factor authentication is enabled for a user.
IUserPhoneNumberStore
The IUserPhoneNumberStore<TUser> interface defines the methods you implement to store user phone numbers. It contains methods for getting and setting the phone number and whether the phone number is confirmed.
IUserEmailStore
The IUserEmailStore<TUser> interface defines the methods you implement to store user email addresses. It contains methods for getting and setting the email address and whether the email is confirmed.
IUserLockoutStore
The IUserLockoutStore<TUser> interface defines the methods you implement to store information about locking an account. It contains methods for tracking failed access attempts and lockouts.
IQueryableUserStore
The IQueryableUserStore<TUser> interface defines the members you implement to provide a queryable user store.

2

u/davidfowl Jul 08 '22

Do you need to implement all of those because you’re using all of them or because you’re enumerating what is possible? Abstractions are purpose built for the scenario because a generic storage abstraction would be really bad for what I would hope are obvious reasons.

1

u/broken-neurons Jul 08 '22

Agreed. I wrote one for WCF and ASP.NET Membership years ago and I appreciate the abstractions and interfaces.

I guess I was talking from a more generic perspective than just authentication. Most of the documentation seems to assume greenfield projects and that everyone uses EF migrations. It’s fine on greenfield projects. It’s horrible on existing databases and applications. I do understand that database model first exists and there are some third party tools to generate EF models and contexts but it feels like a second class citizen as far as Microsoft is concerned.

I think there’s definitely an opportunity for Microsoft to share some of its focus away from only looking at greenfield project developers and also look to support developers who are locked into supporting older Microsoft based BAU systems who want to upgrade to more modern Microsoft offerings or developers from other tech to move across to the ASP.NET platform.

I work on several older ASP.NET applications that are business critical for various clients. I can slowly strangle out bits of those older MVC3 and 4 applications and WebForms ones too. We even have some classic ASP and VB applications running twenty years later, but in order to strangle them slowly out and replace bits of them with more modern MS tech, we’re stuck with those old databases. I’m sure I’m not the only developer dealing with this?

2

u/davidfowl Jul 08 '22

What would you expect in the docs?

1

u/broken-neurons Jul 08 '22

I think I’d have to give it some thought.

First ideas would be to help developers working on other development platforms a migration path to work with their existing databases and help them switch over to .NET bit by bit.

For existing .NET developers improved migration paths from their older tech.

Some of our apps are monolithic behemoths, that have been extended and extended and we’re trying to break them apart, but the move away from older long forgotten ORMs pre EF and early .NET Framework in many cases is just a no go with the upgrade assistant.

The reality is that we need to start porting parts of these monoliths into discrete services but the documentation doesn’t seem to ever think about these (I assume) common use cases that developers out in the field, who don’t have infinite budgets are facing. The core of that problem is that the database doesn’t really change, just the apps using it.

→ More replies

1

u/the_canuckee Jul 08 '22

I just saw the thread on twitter with David and was disappointed at the "can I use cookies" mention. Its the NOT using cookies that drastically makes it more complex and gets into needing to read store/read the jwt from somewhere so you can attach it an http header in the call to the API. If you get a chance I think you should try and do a non cookie approach as that is where all the complexity and confusion lives in my opinion.

1

u/NooShoes Jul 08 '22

Not using cookies is insecure.

→ More replies

1

u/realjoeydood Jul 08 '22

Pain in my ass for decades.

-17

u/jingois Jul 07 '22

Wat?

Use OIDC. Use whatever compliant idp you want. Use an OIDC library in your frontend (oidc-react or whatever). Drop in the couple of lines of code and some config for your dotnet backend.

The only "trouble" I've had with authx in dotnet in the past few years is Cognito lagging due to breaking standards and having to occasionally write a couple of claim transformers.

This post screams of "X library is hard because I want to do dumb shit". Unless your point is that it would be hard to do from scratch - in which case well yes, but actually no. OIDC is complex, doing it yourself would be hard. But if you are intending to write your own auth then you probably aren't dealing with an IdP, so just use forms auth with cookies...

19

u/tysjhd Jul 08 '22

I’ve seen some of your comments around and I don’t know if you realize this, but you can come across as a jackass (like right now). If you’re trying to contribute something meaningful to the conversation you might want to rethink your approach.

→ More replies

4

u/zMisir Jul 08 '22

Yeah and? Where are you going to store tokens issued by oidc library?

1

u/rebornfenix Jul 08 '22

Local storage is secure enough for most apps. If you are really worried, just call the authorize endpoint on your OIDC / OAUTH 2 provider and grab new tokens.

100ms tops on the initial page load for a SPA is nothing. Personally I use local storage to store it and just have the JWT expiration set for 5 min, refresh token at 1 week.

-12

u/jingois Jul 08 '22

Why would that ever be a complex problem that I would give a shit about?

Generally you just hold them in memory (ie: basically do nothing), because almost every IdP will set a cookie, so next time your client starts up they'll run thru the auth flow with zero interactivity in like 50ms.

Or if you want to formalize that shit then set prompt=none.

Or you set up a refresh token endpoint which is also pretty fucking trivial.

Like I said - it just fucking works. If you want to do more complex shit, or store tokens beyond a session then you need to do a little more work.

Honestly I'm fucking amazed. All these mid-level assholes seem to love overcomplicating things and putting in layers upon layers of bullshit to turn an http put into a database insert, yet when an extremely well documented open authx system has a slight bit of complexity the same devs want to shit their pants.

2

u/zMisir Jul 08 '22

It’s not complexity problem. It’s just security concern that me and other mid level aholes are aware of. I’m not gonna explain it cause someone else already did, just google local storage xss.

1

u/jingois Jul 08 '22

hold them in memory

local storage xss

Again - you want to introduce complexity with bullshit token persistence requirements instead of just letting your idp deal with any semi-stateful persistent login crap then that's on you.

If you decide to persist your token between sessions, then yes, unsurprisingly you now have to figure out how to do that securely.

1

u/zMisir Jul 08 '22

I mean the point is that token will be accessible from javascript context, it doesn’t matter where you store it. By storing them in memory you’ll get worst of both worlds

  • no persistence
  • no security

At least with local storage your users will get less frustration if you care less about security

2

u/jingois Jul 08 '22

So we're going with "at least with local storage they will never be in memory". To be extra secure I suggest you don't send them to the API as well.

In-memory is fine.

If you want persistence then you go for xsrf tokens or the refresh token with http only approach - but no motherfuckers is going to be this deep in the comments, and it's easily googleable information - and not fucking complex.

Again, use the provided libraries and it's not complex. Do it from scratch? it's complex... same as trying to talk TDS instead of using sqlclient.

2

u/mmusket Jul 08 '22

is there a non react oidc library you can recommend?

Most of the ones I came across are no longer maintained..

1

u/jingois Jul 08 '22

I've only used react and blazor wasm clients recently. I believe oidc-react is just a wrapper for a generic js library though - so I guess you'd just have to handle state management.

Auth0 have a shitload of guides for everything under the sun - keeping in mind they are trying to sell you on their service.

-25

u/[deleted] Jul 07 '22

[deleted]

21

u/Prod_Is_For_Testing Jul 07 '22

Pretty much all security guides will tell you not to put JWT tokens in local storage

16

u/NooShoes Jul 07 '22

I didn't call JWTs a hack, but putting them in local storage is not best practice. Much better to have them sent in HTTP only cookies.

→ More replies

11

u/yad76 Jul 07 '22

Please stop writing any code that deals with security until you learn the basics. Thanks.

7

u/GiorgioG Jul 07 '22

There’s plenty wrong with putting JWT in local storage. I’m not going to google it for you though.

7

u/NooShoes Jul 07 '22

It's the classic "I can google it for you, but I can't understand it for you".

→ More replies

1

u/the_canuckee Jul 08 '22

Totally agree about your take on implementing a security flow. I was in this situation about a year ago and my head was spinning with concepts and terminology and options and none of it making any sense. I've come a long way but I still think its a very daunting exercise for developers.

1

u/emrikol001 Jul 08 '22

Great topic, hopefully someone can help me on it as well. I am new at a company and have this .net Web API project. I need to add Roles to it (admin, reader, writer etc) and it will be hosted as an intranet only API .

There will be users from within our company that are known within AD though no groups have been created for them to belong to. I can request this later. I also must provide identity management for users outside of our organization who come to visit, work in a designated area and will work via a new frontend app. There is no Identity type of server for me to register their information.

How do I manage both of these types of users that need access to the API?

If I want to use JWT tokens I will need to save some kind of Username & (hashed) Password in a database (this sucks) for the external users. How do I then also provide tokens for my both of my types of (internal & external) users?

Any feedback much appreciated.

1

u/malthuswaswrong Jul 08 '22

The problem is there are 25 years of authentication schemes riding along with .NET and they are all still supported.

It's a frontier. Things are still being invented/decided.

1

u/propostor Jul 08 '22

I wrote a similar thread only a few weeks ago and plenty of folk were in agreement.

You sound a lot more seasoned than I, but my job title is 'Senior' and I too find DotNet auth to be an absolute kludge, way harder than it needs to be, convoluted docs, etc.

Everything else I've ever tried or learned in programming has been fine.

I hate DotNet auth, Microsoft need to go back to the drawing board with it. In fact I hate the entire Microsoft login ecosystem even for their own websites. I can literally feel the DotNet auth under the coves making the entire thing way more convoluted than it needs to be, even just as a standard user. Hate hate hate.

2

u/davidfowl Jul 08 '22

On all of it? What should we change? Break it all again?

1

u/ashsimmonds Jul 08 '22

RemindMe! 2 days

1

u/RemindMeBot Jul 08 '22

I will be messaging you in 2 days on 2022-07-10 15:05:00 UTC to remind you of this link

CLICK THIS LINK to send a PM to also be reminded and to reduce spam.

Parent commenter can delete this message to hide from others.


Info Custom Your Reminders Feedback

1

u/broken-neurons Jul 08 '22

One of the videos I found really useful in getting an understanding of where we are today with auth (as of 2021), was this talk Dominick Baier gave at NDC 2021. Highly recommended. I also liked his subtle joke about having “built a few auth systems along the way”.

https://youtu.be/y2Psj8ACZyw

1

u/short_n_ugly Jul 08 '22

It took 2 solution architects and me (a mid level dev) 1 full day to figure out how to and add a new client secret to authenticate a service which will be calling our API. The authentication solution was written long time by a diff team and the auth part alone is in a separate solution and is around 1000 lines of code - Identity server 4. Made me question this career path and my ability.

2

u/WellYoureWrongThere Jul 24 '22

This sounds like a combination of challenges.

Maybe all three of you didn't have much experience with auth in the first place? Or Auth with .NET. Working with any legacy solution is hard to let alone an Auth legacy solution. Maybe when it was built, it was also built poorly or by someone who didn't really understand it either. Although identityserver 4 isn't that old so it can't be that legacy (or our definitions of legacy vary greatly).

Auth on its own is complicated enough. Auth in .NET is also hard. I think the biggest problem is devs jumping in and trying to figure it out on the fly without having a firm understanding of the concepts.

Check out the Duende Identityserver samples repo.

2

u/Loris156 Jul 08 '22

I feel this. Question: What's your approach for securing an ASP.NET Core API with a SPA frontend and a mobile app?

JWT in localStorage?

JWT in Cookies, what about the mobile app?

Accept both Authorization header JWT and Cookie JWT?

What to do with refresh tokens?

Can't find the best way to do this

1

u/JeffyJones407 Jul 08 '22

I stumbled upon this because of a Twitter thread that the algorithm suggested, with David Fowler in it. I give him a lot of credit, because he seems very patient. I wrote a little library just to take the auth secret, make the post to the third party, and get claims back to use in my own user management and auth bits. I still rely on the .Net auth cookie stuff, and the rest is not well documented. They insist that it's not tightly coupled to EF, but you won't find any docs that explain that.

I think OAuth is also not something that should just be abstracted. If you don't understand how it works, it's harder to figure out what's wrong with the abstraction breaks.

1

u/TurnipSwagger Jul 08 '22

I tried using the authorization code flow for Google Accounts with a React frontend and .NET backend, and after a few days I gave up. I ended up subclassing Microsoft.AspNetCore.Authentication.Google.GoogleHandler and reused their implementation in a way it would actually work.

Just look at the god awful mess they created in their implementations: https://github.com/dotnet/aspnetcore/tree/main/src/Security/Authentication

If someone in my team would write code like this, I would get rid of them asap.

Let them provide an example where the frontend gets the authorization code (without server redirect. It is a SPA!) and the backend can exchange this code for refresh and access tokens, This should not be so difficult

2

u/davidfowl Jul 09 '22

Can you provide that feedback to the team on GitHub with details on what you were doing and what was difficult? I promise I wont tell them what you said about firing them for writing code like this 😅.