Authentication history (Basic, Digest, Cookie, Session, Token, JWT, API key)

iamprovidence
10 min readFeb 17, 2024

--

Let’s say you are building an app. Nothing overcomplicated. Simple data storage like Google drive. Users can upload and view their files. The question is how do we make sure users can only view their files and not someone else?

The naive approach would be to send UserId in every request to a server. It is not only naive but also creates a huge security hole! As soon as your users have basic knowledge of how the Internet works they will try sending requests to a server with a different UserId.

The only valid option would be to let the server distinguish which user sent a request. There is a variety of authentication options, like Basic authentication, Digest, Cookie, Session, Token and JWT. If you are interested in getting deeper knowledge in what are those, which problems they solve, and their benefits and drawbacks, read this article to the very end.

In case you are new here, there are other articles on this topic.

Authentication theory:

How to implement authentication in C# environment:

You may also check out some others stories:

  • What is authentication schema in ASP about?
  • Authorization policy under the hood
  • Encapsulate authentication with DelegatingHandler
  • How to parse token on client side

Meanwhile we are starting.

Basic Authentication

The simplest authentication mechanism is HTTP Basic Authentication. Here’s an overview of how it works:

  1. A user enters his login and password in the login form
  2. If the server successfully validates those, the client now can store the user’s login and password
  3. User Authentication. The client includes an Authorization header in all subsequent requests with the word "Basic" followed by a space and a base64-encoded string of the form "login:password".
HTTP: https://my-app.com/get-photos

Authorization: Basic base64encode(login:password)

4. Server Verification. The server, upon receiving the request, decodes the base64 string, retrieves the login and password, and verifies them against DB. If the credentials are valid, the server allows access to the requested resource; otherwise, it returns a 401 Unauthorized status code.

Despite its simplicity, Basic Authorization has multiple drawbacks:

  • security. credentials are sent in an easily decodable Base64-encoded format therefore should only be used in conjunction with HTTPS
  • privacy. user’s credentials are exposed and can not be used by other systems
  • revocation. the server has no way to end the user’s session
  • storage. user’s credential is stored in a browser and could be exposed
  • performance. additional db calls have to be made to validate credentials (consider adding cache)

Digest Authentication

To overcome the shortcoming of the Basic Authentication a more secure way to transmit credentials was found.

It involves hashing the username, password, and other information in the request, making it harder to intercept and decode:

  1. A user enters his login and password in the login form
  2. If the server succesfully validates those, the client create a hash based on login and password
  3. User Authentication. The client includes an Authorization header in all subsequent requests with the word "Digest" followed user’s login and generated hash:
HTTP: https://my-app.com/get-photos

Authorization: Digest username="john", hash="5ccc069c403eb171e9517f40e41"

4. Server Verification. The server, upon receiving the client’s request, performs its own hash calculations using the stored password. If the calculated hash matches the one sent by the client, access is granted.

In practice it is a bit more complicated than that, but it is enough for you to understand Digest Authentication.

This time we don’t send user’s password but a hash value, which is irreversible comparing to encoding. This approach is a bit more secure, but we still have issues:

  • performance. validation of credential cost time and resources
  • revocation. the server still has no way to end the user’s session

Cookie

The traditional authentication approach on the web involves cookies.

A cookie could be anything, a number, string, serialized object, and so on. However, for security reasons, the cookie responsible for authentication is encrypted.

The usual flow of using cookies is as follows:

  1. A user enters his login and password in the login form
  2. If the server successfully validates those, it issues an encrypted cookie. That cookie can also contain any additional information about the user like age, permissions, preferences etc
  3. User Authentication. The user’s browser stores that cookie and includes it in all subsequent requests as a Cookie header:
HTTP: https://my-app.com/get-photos

Cookie: uLfpKTEAAxgVbjjJs6S5RcXnQlLnK4knnz2zBa5wN3wO/zqQM4ZhAv604

4. Server Verification. The server, upon receiving the client’s request, validates a cryptographic signature attached to the cookie, its expiration time, domain path etc. If the cookie sent by the client is valid, access is granted.

There are a few things worth noting:

  • self-contained. cookies are self-contained. It means the server can just verify its signature without the need to do a DB request. This is how validation cost issues were solved
  • stateless: the server does not need to store additional data. This simplifies server-side implementations and allows for easier horizontal scaling
  • revocation. server has multiple ways to terminate a user’s session (cookie expiration time, timeout session for inactive users, send invalidate cookie header)
  • in-build support. but the most lovely thing about cookies is that they are so common you don’t have to implement any code on the client side. Your browser will attach cookies on every request automatically. That is just ideal for MPA

Regardless of how good it sounds, there are always some caveats:

  • performance. cookies are sent out for every single request (even for requests that don’t require authentication)
  • interoperability. cookies work out of the box only in the browser. For all the other clients (desktop, mobile etc) you still would have to write code
  • scale poorly. cookies are bound to a server’s domain and, therefore are not applicable in SPA or microservices

You can find an example of cookie-based authentication here.

Session

You can make cookies work in a distributed environment with sessions:

  1. A user enters his login and password in the login form
  2. If the server successfully validates those, it gets or creates a user’s session in the database and issues a cookie with the session id
  3. User Authentication. The user’s browser stores that cookie and includes it in all subsequent requests as a Cookie header:
HTTP: https://my-app.com/get-photos

Cookie: SessionId=270299

4. Server Verification. The server, upon receiving the client’s request, validates the cookie and retrieves session from the DB

A session is just a record in the database about a user. It contains all the data you would usually have in the cookie, but this time it is completely on the server side.

Despite weak advantages:

  • server-side storage. sensitive data is not exposed to the client
  • centralized control on the server side. it is easier to implement features like session timeouts, account locking, and other security measures
  • cross-domain communication. a session can be utilized across a distributed environment

You have almost the same issues as before:

  • latency. session validation requires DB call
  • performance. cookies are sent out for every single request
  • interoperability. cookies do not work on their own outside the browser
  • scale poorly. a database can be distributed too, which leads even to more issues

Token

Long story short, token authentication is the same as the session one. We just no longer have cookies, and instead of session id, a random string is used, so it is harder to compromise it:

  1. A user enters his login and password in the login form
  2. If the server successfully validates those, it gets or creates a user’s session in the database and issues a random string related to this session called a token
  3. User Authentication. The client stores that token and includes it in all subsequent requests:
HTTP: https://my-app.com/get-photos

Authorization: pS4zFXYx0URtDEkMWOcGunMgxA7cGyXYx0URtDEkMWOcGunMgxA7

4. Server Verification. The server, upon receiving the client’s request, validates there is a session in the DB with such token. If so the access is granted

I know 😔. Cookies seemed to be such a good idea. So why would we get rid of it and use a token instead?

  • interoperability. token can be used on any device (browser, mobile, desktop)
  • cross-domain communication. support distributed architecture
  • privacy. no credential is exposed, so the token can be used by third-party
  • server-side storage. sensitive data is not exposed to the client
  • centralized control on the server side. it is easier to implement features like session timeouts, account locking, and other security measures

However, we just get back to square one:

  • no in-build support. we have to implement everything by ourselves
  • storage. token stored on the client side can be exposed
  • performance. token validation costs a DB request. For microservices, it is even worse. You need an http call to an identity server that will do a DB request

JWT

Finally, we grow up to good old JWT tokens. 😌

So what is the deal with it? JWT tries to keep the benefits of token authentication but decreases performance issues. Do you remember how exactly the validation cost was reduced with cookies? 🤔 Cookies are self-contained. So let’s make our token self-contained.

JWT (JSON Web Token) is a cryptographically secure token that contains all needed data inside itself in JSON format:

{
"alg": "HS256",
"typ": "JWT"
}
.
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
.
signature

Additionally it is encrypted to make it more size concise. So the token above will be encoded to:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The flow is pretty much the same as what we are used to:

  1. A user enters his login and password in the login form
  2. If the server succesfully validates those, it issues JWT token
  3. User Authentication. The client stores that token and includes an Authorization header in all subsequent requests with the word “Bearer” followed by a space and a JWT token:
HTTP: https://my-app.com/get-photos

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

4. Server Verification. The server, upon receiving the client’s request, validates the signature and grants access

The benefits are:

  • performance. no database lookup since the token is self-contained
  • interoperability. the token can be used on any device (browser, mobile, desktop etc)
  • stateless: the server does not need to store additional data. This simplifies server-side implementations and allows for easier horizontal scaling
  • cross-domain communication. support distributed architecture
  • privacy. no credential is exposed, so the oken can be used by third-party

But remember about those issues:

  • storage. token still can be hijacked by an attacker (can be partially fixed with a refresh token)
  • revocation. the server has no way to end the user’s session (JWT token is valid even after a user logs out)
  • no in-build support. we need to write code (one can argue it is a benefit since it is more agile and flexible but I will let you decide)
  • sensitive data exposure. care must be taken not to include sensitive information in the token payload

As you can see, even though JWT is so popular, it is not a panacea either.

API Key

Since we are already on this topic, a few final words for API Key authentication.

The API Key flow is avoided for user authentication but is commonly used for machine-to-machine (M2M) communication or third-party integrations, where one software application or system interacts with another API (Application Programming Interfaces).

Here is a basic overview of the API Key flow:

  1. Developers or administrators generate API keys beforehand
  2. Server Authentication. The client includes an Authorization header in all subsequent requests with the word "ApiKey" followed by the key:
HTTP: https://my-app.com/get-photos

Authorization: ApiKey 9c403eb171e9517f40e415ccc069c403eb171e9517f40e41

You can also find an implementation where the API key is included as a query parameter:

HTTP: https://my-app.com/get-photos?
api-key=9c403eb171e9517f40e415ccc069c403eb171e9517f40e41

3. Server Verification. The server, upon receiving the client’s request, validates the API key to check if it is legitimate and associated with a valid application

Even though it is almost a standard it still has some minute flaws:

  • storage. can be stored insecurely, such as hardcoding them in source code, storing them in configuration files, or sharing them inappropriately
  • no expiration. they don’t expire unless explicitly revoked allowing hijackers to have infinite access to the API

Final Thoughts

And that is all for today 😌. In this article I was aiming to guide you through the authentication landscape, offering insights into the diverse tools and techniques that have shaped the authentication over time.

It is essential not only to understand and be able to use any of those approaches in parallel but also to combine them. Regardless of how clean and experience-proved those ideas look, in practice, you would have to deal with a complete horror of legacy code 😁.

It happens so, that authentication is so widely known and a completely misunderstood topic at the same time, that most of the time it results in a complete mess. I have seen jwt is stored in cookies, cookies used for API-only communication, or jwt for m2m. I have been through that kind of sh*t you don’t even want to hear. Most of the time those implementations were done by me 😅. Don’t do as I have done. Be smarter. Learn ahead and find a solution that suits you best 🙃.

Let me know in the comment what was your worst experience with authentication?💬
Give this article a clap or a few if you like it 👏
You can support me with a link below ☕️
And don’t forget to follow if you are interested to read about all those authentication methods in details ✅

--

--

iamprovidence

👨🏼‍💻 Full Stack Dev writing about software architecture, patterns and other programming stuff https://www.buymeacoffee.com/iamprovidence