Avoiding The DIY Authentication Trap


Avoiding The DIY Authentication Trap

Read in your browser

With Course 3 of the bootcamp finally launched, I'm now switching gears to Course 4, which will be all about deploying .NET apps to the Azure cloud, one of the most exciting parts of the bootcamp.

One key thing you need to master before doing any sort of cloud development is Docker, which I believe most developers are not using yet. That's why I spent most of this week working on tons of new Docker-specific content, learning a few new things myself along the way.

But as I was wrapping up Course 3 I kept finding tutorials and even paid courses that teach you how to implement a login endpoint in a backend API to generate JWTs, which will then be used to access protected endpoints.

This is fun for learning how to craft JWTs, but I'm concerned many folks, especially beginners, might assume that is the right way to move forward with their authentication strategy.

So today I'll show what's wrong with standing up your own login endpoint and what's the right thing to do instead.

Let's dive in.

The DIY Authentication Trap

OK, so you are working on your new web app, which is made of an ASP.NET Core back-end and let's say an Angular, React or Blazor front-end.

You have protected the back-end API endpoints so they will only allow requests with valid JWTs, so you need to come up with a way to both:

  • Authenticate your users
  • Generate JWTs after authentication

Here's the naive way to implement this:

So, essentially:

  1. The user enters a username and password in a login form on our front-end and submits.
  2. The front-end sends a POST request to the back-end with those credentials.
  3. The back-end takes the credentials and validates them.
  4. If valid, the back-end generates a JWT and sends it back to the front-end
  5. The front-end uses the JWT to make requests to protected endpoints in the back-end.

What's wrong with this?

Many things:

  • You're a credential sponge: Any attacker who compromises your API gets plain-text passwords, even through HTTPS.
  • Brute force paradise: Without proper IAM-level rate limiting, attackers can hammer your endpoint with leaked password lists.
  • Compliance nightmare: Processing raw credentials means dealing with strict security compliance and audit requirements.
  • Liability magnet: When breached, explaining why you didn't use established identity providers won't go well.
  • Token security is hard: Your JWT code probably mishandles key rotation, revocation, and session management.
  • Future-proofing fail: Adding MFA, social login, or SSO later becomes a massive headache.
  • Delayed defense: When new vulnerabilities emerge, you're stuck waiting for your next deployment instead of getting instant provider updates.

Let's go over a better way to do this.

The Middle Ground: ASP.NET Core Identity

ASP.NET Core Identity, a built-int ASP.NET Core component, provides APIs that handle authentication, authorization, and identity management.

When you use that, you don't have to write your own authentication endpoints, including login; instead, the framework will provide the already implemented endpoints.

So the flow would look like this:

In this case:

  1. The user enters a username and password in a login form on our front end and submits.
  2. The front-end sends a POST request to the back-end with those credentials.
  3. The back-end receives the request directly in the ASP.NET Core Identity Login endpoint.
  4. ASP.NET Core Identity validates the credentials.
  5. If valid, ASP.NET Core Identity generates a proprietary bearer token and sends it back to the front-end
  6. The front-end uses the bearer token to make requests to protected endpoints in the back-end.

Is this better than the first approach?

Yes, because you are no longer re-inventing the wheel with your own implementation to generate tokens. Whatever ASP.NET Core identity is doing there is safe and should follow best practices.

Should you use it?

You can, but I wouldn't. Here's why:

  • Still handling raw credentials: Your API is still receiving and processing plain-text passwords, even if ASP.NET Core Identity handles them properly afterward.
  • Your database, your risk: You're still responsible for taking good care of your own Users database, including password hashes and sensitive user data.
  • No token customization: You can't customize what comes in the generated tokens. They come as-is with minimal claims that tell nothing about what the user is allowed to do.
  • No external authentication support: There's no social login, SSO, or any other modern authentication support. You are essentially locking yourself into always requiring a username and password.
  • Harder to scale: When your application grows, migrating from ASP.NET Core Identity to a proper identity provider becomes more complex.

In fact, here's what the official docs say:

The tokens aren't standard JSON Web Tokens (JWTs). The use of custom tokens is intentional, as the built-in Identity API is meant primarily for simple scenarios.

So I would only use this to come up with a quick demo for my boss, thinking I'll need to use something else for the real deal.

What to do instead?

The Right Way: OpenID Connect

There's no need to reinvent the wheel. OpenID Connect is the industry standard for authentication and authorization and should be used across all your protected apps.

The whole authentication flow is a bit complex, but at a very high level it looks like this:

Notice that in this case, we have a 3rd actor: the Authorization Server.

This is a separate service completely isolated from your back-end and whose only purpose is to securely authenticate and authorize your users, as well as generate standard JWTs.

So this time:

  1. The user clicks a Login button in your front-end.
  2. The user is immediately redirected to the login page in the authorization server.
  3. The user enters username and password and submits. Notice that any other form of authentication could also be enabled in the authorization server.
  4. The authorization server validates the credentials.
  5. If valid, the authorization server generates a JWT and sends it back to the front-end.
  6. The front-end uses the JWT to make requests to protected endpoints in the back-end.

Why is this the best option?

Many reasons:

  • Credentials never touch your app: Users enter sensitive data directly into the authorization server, completely isolating your application from credential handling.
  • Standards-based security: OpenID Connect and OAuth 2.0 are battle-tested standards used by tech giants worldwide, with continuous security improvements.
  • Rich features out-of-box: Social logins, MFA, password policies, brute force protection, and other security features come built-in.
  • No credential storage: User credentials are stored and managed securely by the authorization server, reducing your security and compliance burden.
  • Flexible token claims: JWTs come with standard claims plus easy customization options, without writing custom code.
  • Single Sign-On ready: You can easily add new applications to your ecosystem with centralized authentication.
  • Universal compatibility: Standard tokens work with any platform or framework, making it easy to add new services or migrate existing ones.

Which authorization server to use?

It doesn't matter, as long as it is OpenID Connect (OIDC) certified. I go over how to configure Keycloak and Entra ID for OIDC in the bootcamp, but you can choose from a myriad of other options like Duende Identity Server, Auth0, Google, Github, and many, many others.

But I want my own login form!

I know, but it's not safe. You cannot be responsible for receiving credentials from your users, under any circumstance.

Besides, all authorization servers will offer you multiple ways to customize their login forms to match your app style.

For instance, look at what happens when you try to log in to your ChatGPT account:

You get redirected to Auth0! And you can either login with your ChatGPT account or with any of the social logins ChatGPT has enabled in Auth0 for their app.

OpenID Connect is the way!

Wrapping Up

Please be safe and avoid a lot of future headaches by not reinventing the wheel. There is no reason to implement your own login endpoint and generate your own JWTs these days, regardless of what many tutorials or courses tell you.

This is why I decided to dedicate an entire course to go over all of these critical security matters, and many other things you can't miss before going to the cloud.

Talking about the bootcamp, I better get back to working on those new Docker slides.

Stay safe!

Julio


Whenever you’re ready, there are 3 ways I can help you:

  1. .NET Cloud Developer Bootcamp:​ Everything you need to build production-ready .NET applications for the Azure cloud at scale.
  2. Ultimate C# Unit Testing Bundle: A complete beginner to advanced C# unit testing package to write high-quality C# code and ship real-world applications faster and with high confidence.
  3. Promote yourself to 20,000+ subscribers by sponsoring this newsletter.

11060 236th PL NE, Redmond, WA 98053
Unsubscribe · Preferences

The .NET Saturday

Join 20,000+ subscribers for actionable .NET, C#, Azure and DevOps tips. Upgrade your skills in less than 5 mins every week.

Read more from The .NET Saturday

Fixing Claims the Right Way Read in your browser It's flu season in the US and after my 5yo went down with a cold, sure enough, one of my other kids followed and then it got me. So it's been a tough week, but hope to be recovering by the time you read this. On top of that, seems like it's time for a full Windows reinstall because this box has been giving me so much trouble lately. But before getting into that adventure, I thought it would be best to finish this newsletter, where I'll cover a...

IsAuthenticated Is Not About The User Read in your browser A few days ago I finished the audiobook version of Nexus, the latest book by Yuval Noah Harari, and wow, it's such an impressive take on the impact of AI in our society and what could come next. One potential scenario mentioned in the book is the creation of AI-powered social credit systems, where governments or organizations score individuals based on their behaviors, decisions, and interactions. Governments today are already...

Understanding JSON Web Tokens Read in your browser Well, 2024 is gone, and wow it was such an amazing year in many aspects, but especially for software developers and AI enthusiasts. As I look back, here are a few 2024 breakthroughs that I think set the stage for how we will be doing things moving forward: GPT-4o and Claude 3.5 Sonnet enabled more natural and versatile human-AI interactions, significantly changing the way we solve our everyday human problems. AI coding assistants like GitHub...