> For the complete documentation index, see [llms.txt](https://naz.gitbook.io/my-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://naz.gitbook.io/my-docs/backend-learning/authentication_and_authorization_for_backend_engineers.md).

# Lesson 4: Authentication and Authorization for Backend Engineers

## Authentication and Authorization in Two Sentences

Authentication and authorization is one of those areas encountered every day. Login screens, signing up on different platforms — those are basically the **authentication flows**.

To summarize everything in two sentences: **authentication** is a mechanism to assign an identity to a subject. In technical terms it answers the question **"who are you"** in a given context — the context can be a platform, an operating system, or a phone. The process of finding the answer to that question is called authentication.

**Authorization** answers the question **"what can you do"** in that context — all capabilities, all permissions, in the technical jargon. The process of finding the answer to that question is called authorization. Those are the two sentence summaries of the whole topic.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-7d2e0d96b721e1b1950b6656a77de141d0f24520%2F01-authentication-and-authorization-in-two-sentences.png?alt=media)

***

## Historical Context of Authentication

Before the technical part, a look at what authentication was, how it reached modern methods, and how the dots connect together.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-746f17aa11a529114d0c4f68c5f54e11a8793ed8%2F02-historical-context-of-authentication.png?alt=media)

### Implicit Trust in Pre-Industrial Societies

The story starts somewhere in pre-industrial societies where authentication was pretty intrinsic — it was implied. An identity of a person or subject was the same as the subject's recognition.

Someone already respected in the community, let's say a village elder, could vouch for a person. Deals made during those times were sealed with a handshake, an act symbolizing mutual recognition and agreement. Even though it was pretty elementary, this method leveraged a form of authentication based on **human contextual trust**.

As populations grew and interactions extended beyond familiar circles, beyond the village, implicit trust mechanisms failed to scale — the village elder is not trusted in all communities, countries or continents. This marked the beginning of the search for **explicit authentication**. People needed proofs of identity that could function independently of personal acquaintance.

### Seals and the Cryptographic Era

By the medieval period, society needed a system that scaled beyond personal recognition, beyond people of trusted reputation vouching for other people. People came up with a system called a **seal** — a wax seal attached to different letters and agreements as some form of authentication.

It was one form of early cryptographic mechanism: a seal of a unique pattern put on documents which acted as signatures. Today there are digital signatures and handwriting based signatures; this was one kind of signature during the medieval period.

Seals functioned as the **first widely adopted authentication tokens** — physical representations of identity that relied on **possession**, **something you have**. If the seal is in a person's possession, that person is said to be authenticated.

Seals had vulnerabilities: they were prone to forgery, marking the **first recorded instance of authentication bypass attacks**. A bypass attack means skipping the authentication with malicious intent. This led to the evolution of more sophisticated mechanisms including watermarks and encrypted codes used in trade documentation, setting a foundation for cryptographic thinking.

### Pass Phrases and Shared Secrets

During the Industrial Revolution a lot of progress happened in machines, and communication systems also started to evolve into a more secure and advanced form. A technology called the **telegraph** was introduced and became critical infrastructure, and with it came the need for secure message validation.

Operators used pre-agreed **pass phrases** — an early form of **shared secrets**. These pass phrases were the same as static passwords: one static string, one static password decided on once.

That relied on the principle of **something you know**, where earlier the principle was **something you have** — the wax seal, some token possessed and shown to verify identity. The whole principle of authentication changed into something known, inside the brain or in some form of written or verbal communication. That is the principle that in modern times is known as a password.

### Mainframes and the First Digital Passwords

The first form of computation was mainframes somewhere in the mid 20th century, and authentication entered its first digital phase.

In 1961, researchers at MIT's Project MAC were working with something called Compatible Time-Sharing Systems, also known as **CTSS**, and they introduced the concept of passwords for multi-user systems. The goal was a system that allowed multiple users to use a computer without sharing each other's data.

They stored passwords in plain text, which was in itself a very critical vulnerability. That came into view when one of them printed the password file in plain text — it was a plain text file, so the password could be printed. This incident marked the **genesis of secure password storage mechanisms**: the philosophy that passwords should not be stored in plain text.

That led to innovations like **hashing**, used frequently today to store passwords in databases. A hashing algorithm takes a plain text string and returns a hashed string: a computational algorithm transforms the string into a **fixed length** representation. The length does not change whether a three character string or a 100 character string is provided, depending on the hashing algorithm, and the same string produces the same result every time. The result is irreversible.

During this era authentication began to align with the principles of **confidentiality, integrity and availability** — the core tenets of information security.

### Asymmetric Cryptography and Kerberos

In the 1970s an explosion in cryptographic research happened, driven by the work of Whitfield Diffie and Martin Hellman. Their invention of the **Diffie-Hellman key exchange** introduced the concept of **asymmetric cryptography**, enabling two parties to establish a shared secret over an untrusted medium.

Asymmetric cryptography became the backbone of modern authentication protocols — the protocols in use now are all based on asymmetric key cryptography, also called **PKI, public key infrastructure**.

This era also gave rise to protocols like **Kerberos**, which introduced **ticket based authentication**. Kerberos relied on trusted third parties to issue tickets that verified both the user and the service identity — a precursor to token based authentication systems.

### Multi-Factor Authentication

Around the 1990s the internet was growing and a need for scalable authentication methods was rising. Simple username and password based systems were not enough, and not strong enough against **brute force** and **dictionary attacks** that were newly coming during those times.

That led to **MFA, multi-factor authentication**, which combined different principles:

* **Something you know** -- passwords or PINs.
* **Something you have** -- cards, smart cards, or OTP generators.
* **Something you are** -- personal identification, biometric data like fingerprints or retina scans.

Biometric authentication emerged as a groundbreaking development, leveraging pattern recognition algorithms and statistical models to identify users based on unique physical traits. However biometric systems introduce challenges of false positives, false negatives and template security — biometric security was not the one step solution for all the problems emerging during those times.

### Modern Authentication Frameworks

In the 21st century, the rise of cloud computing, mobile devices and API based architectures demanded advanced authentication frameworks. None of the earlier methods or principles were enough, so more technically robust implementations emerged:

* **OAuth and OAuth 2.0** -- the most popular ones still in use today.
* **JWTs**
* **Zero trust architecture**
* **Passwordless authentication** such as WebAuthn, eliminating passwords entirely, relying on public key and private key stored in hardware devices.

### The Future of Authentication

A couple of candidates exist for the future of authentication:

* **Decentralized identity** -- created using technologies like blockchain, offering robust features for authentication. Still in early stages, people are still experimenting, but promising.
* **Behavioral biometrics**
* **Post quantum cryptography** -- once quantum computers get used heavily they are going to break the cryptographic algorithms in use now, the RSA and public key private key algorithms that provide the sense that data and passwords are secure. Those algorithms were not made for that level of computation, and quantum computers are very fast. Post quantum cryptography is the idea of cryptographic techniques that are even secure for quantum computers; some algorithms already exist.

***

## Sessions, JWTs and Cookies

Three components recur in every authentication and authorization flow: **sessions, JWTs, cookies**.

### Sessions

When the web started, HTTP emerged as the backbone of all communication between client and servers. By design HTTP was **stateless**: HTTP treated every request as an isolated interaction, it did not remember the previous request, each request had enough and all the information the server needed to perform the business logic. It had no memory of past exchanges.

This was ideal for the earlier web, which mostly consisted of static pages, static images and readable data. Continuity and memory between different requests was not a need when it was designed.

When the web transitioned into **dynamic content**, statelessness became a bottleneck. Highly interactive websites like e-commerce sites need to remember the items in a cart, and a user needs to remain logged in while navigating between different pages. These questions marked the beginning of **stateful interactions** in the web, and that is why the concept of a **session** came into play.

A session provides a way to establish **temporary server side context** for each user, some memory the server has about the user when the user makes a request next time.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-68e51cdb7d16dc8a6bfdcc031bae47cc4c0a825a%2F03-sessions.png?alt=media)

Three phases:

1. **Session creation** -- when a user logs in the server creates a unique **session ID** and stores it alongside the relevant user data — role, name, cart items, whether the user is authenticated. It is stored in a persistent store: a database, or Redis, an in-memory persistent store compared to a database which is file based.
2. **Session ID to the client** -- the unique key is sent to the browser as a **cookie**. All requests the client makes after that include the cookie, so the server can use the session ID to fetch the user data from the persistent store.
3. **Expiry** -- sessions are **short lived**, they have an expiry date. If the expiry is 15 minutes, after 15 minutes the session ID is expired and the server creates another session.

Session storage evolved:

* **File based sessions** -- the early implementation, simple but suffering from scalability issues as the number of users grew.
* **Database backed sessions** -- sessions moved to databases, providing faster lookups and persistent storage across server restarts. The session stayed available even after the server restarted.
* **Distributed storage** -- session storage moved to distributed storage like **Redis** or **memcached**, in-memory stores meaning data stored in RAM compared to hard disk, comparatively faster than database lookups.

Sessions are still used today for the same purpose: to provide some kind of memory to servers.

### JWTs

By the mid 2000s web applications had grown into globally distributed systems, and stateful systems caused a bottleneck:

* **Memory** -- retaining session data for millions of users became costly.
* **Replication** -- synchronizing session data across servers in different parts of the world introduced latency, delays in authenticating the user.
* **Consistency** challenges because of that synchronizing need.

That led to the birth of **JWTs**, because developers sought a solution that could offload state from the server while maintaining security and integrity. JWTs, formalized in **2015**, are a **stateless mechanism for transferring claims** between two parties or two systems.

The key innovation was **self-contained tokens**: JWT tokens contain user data like user ID and role, plus the cryptographic signature, in one base64 encoded token.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-5d6365283f2488f6507edca36c1adf2fffafae17%2F04-jwts.png?alt=media)

The three parts:

* **Header** -- metadata such as the **signing algorithm**, so the server knows what algorithm was used while the JWT was created.
* **Payload** -- the data actually needed. The **sub** field stores the user ID, which can be a user ID from a database or from an auth provider, any context — it is just a storage mechanism. The **iat** field stores when the JWT was issued, the full form being "issued at". Optional fields store the name of the user and the role — admin, member, editor, viewer or writer.
* **Signature** -- verifies whether the JWT was issued by the right party and whether the data has been tampered with, using a **secret key** kept only to the issuer. If someone makes any change to the JWT and it is verified with the secret key, the validation fails — it is not a valid JWT anymore.

That is how user data is verified in a stateless manner: there is no need to store session information anymore. Verification happens with every request, it is very lightweight and saves a lot of storage cost.

Advantages that made JWTs popular:

* **Statelessness** -- no server side storage costs.
* **Scalability** -- microservice and distributed architectures mean a cookie issued from one server cannot be used by a server in another domain. A JWT can be sent to different servers, and all the servers have the secret key of the JWT using which they decode it and verify the user's identity. A server can be scaled to many instances without worrying about user authentication.
* **Portability** -- because of the lightweight, URL friendly, base64 encoded string format, JWTs can be passed between different systems and stored in cookies or other limited space storage systems. They can be stored in local storage, even though that should not be done.

Challenges introduced by JWTs:

* **Token theft** -- since JWT is stateless there is no stored mechanism the server uses to validate identity. If someone has access to a JWT they can impersonate the user and perform actions on their behalf, and there is no mechanism to invalidate that token until it expires, unless the server changes its secret. The problem there is that changing the secret forces all users of that system to log in again and get new JWTs, just to invalidate one token.
* **Revocation** -- if the server wanted to revoke some user's access, it could not do that. Since JWTs are stateless there is no way to track the status of a JWT token until they expire.

### The Hybrid Approach

A hybrid approach combines **statelessness with statefulness**:

1. The user logs in and the server sends a JWT token which the user stores. With every subsequent request the user sends the JWT in an **authorization** header or as a cookie. The server verifies the JWT with its secret key and identifies the user. In a typical JWT workflow no additional storage lookup is done — the user ID inside the JWT is enough.
2. In the hybrid approach, after the verification step passes, a **blacklist of JWTs** is maintained in a persistent storage — a database call or an in-memory call for Redis or memcached. That can temporarily block users or revoke access in case an account is hacked or a user does something malicious.

The question it raises: the whole point of using JWT was the statelessness, and if a persistent storage lookup is needed to check validity, why not use the stateful approach in the first place, which provides that advantage and is considered more secure?

The typical advice is not to think about all these different authentication strategies. Go with an **auth provider** — Auth0, or modern alternatives like Clerk. The algorithm, the hash, the salt, all the different parts that create a secure authentication system — that is their headache. For a medium to a largely complex system it is a good idea to go with an external auth provider. While learning backend engineering it is a good idea to implement an auth system to understand how the different parts work, the tradeoffs, the advantages and disadvantages — and then in a production system go with an auth provider unless very confident about the authentication workflows.

### Cookies

A cookie is some way of storing a piece of information — any string, any value — in a user's browser **from the server side**. That is the important part: using cookies, servers can store information in client browsers.

Given certain conditions, that cookie is only accessible to that server — a server cannot see other servers' cookies, which is a security feature offered by browsers. A cookie set by one server in a client's browser will be sent in all subsequent requests to that server.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-85d9870d11896940404212b0b95be30a376969bf%2F05-cookies.png?alt=media)

During the authentication workflow: the client authenticates with username and password, and if successful the server sets a cookie with the **authorization token** — a JWT token or a session ID, depending on the implementation. The client's browser — Chrome, Firefox, Brave, whatever it is — sends this cookie key to the server with subsequent requests so the server can validate the authenticity of the token, identify the user and authorize them.

That automates the process of the server sending the token to the client and the client sending it back with every subsequent request, all done from the server side.

***

## Stateful Authentication

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-36b1896234fbbf0e0a953362087e3b6b2a8387b9%2F06-stateful-authentication.png?alt=media)

1. The client — a browser, let's say Chrome — sends username and password to the server.
2. The server checks the validity and correctness of the password and the username or email. If the user is eligible to authenticate, the server generates a **session ID**, bundles the user data and stores it in **Redis**. It can be a database too, but mostly platforms use Redis because of the fast read time and fast access time compared to traditional databases.
3. The server sends the session ID back to the client in a **cookie** — an **HTTP only cookie**, which means JavaScript cannot access the value of the session ID.
4. Because of the nature of the cookie, all requests that happen after that one will have this cookie where the session ID is present.
5. The server takes that session ID and checks the existence of that session ID in Redis, the expiry, and the user data. It can identify the user, authorize the user, and the user has the ability to call the API.

The session ID can be any cryptographic random string, it can be a JWT token, whatever depends on the implementation.

***

## Stateless Authentication

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-1ad0d11710ad6773c248e9d4586a2b22a289b7d5%2F07-stateless-authentication.png?alt=media)

1. The user sends the username and password in the first request while trying to log in. The server checks the authenticity of the username and password, and if they are correct it generates a **signed JWT token** with a secret key. The server has its own secret key using which it can sign and verify JWTs — this is very important, it has to be stored somewhere securely.
2. The server creates a new JWT token with the user's information — user ID, user role, etc. — and sends it back to the client.
3. In all the next requests, if the client wants to identify and authorize itself, it sends the JWT back in a header. The standard is a header called **authorization**.
4. The server extracts the token and tries to verify it using the secret key it has. If the verification is successful, authentication is successful and the user has a successful API response. Otherwise the server throws an error called **unauthorized** or **forbidden**, whatever the convention is.

It is called stateless because in stateful authentication a persistent store has to be looked up — a database or Redis. Here, whatever information is needed about the user is stored in the token itself, which is a signed token. To verify the authenticity of the token a secret key is used, so it is a **self sustainable, self containing token**.

***

## When to Choose Stateful or Stateless

| Aspect           | Stateful                                                                                                                                     | Stateless                                                                                                                    |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **Pros**         | Centralized control over all sessions, real time information of all active sessions, easy to revoke access, revoke a session, log out a user | Scalability, no session store dependency, ideal for distributed architectures, mobile friendly where cookies are not a thing |
| **Cons**         | Limited scalability, higher operational complexity with distributed systems, latency with multiple servers in multiple regions               | Token revocation is complex, no way to revoke access until the token expires                                                 |
| **Suitable for** | Applications with strict session requirements                                                                                                | APIs and mobile clients                                                                                                      |

Most applications should go with **stateful authentication** because of the secure nature and the convenience of revoking tokens and revoking access of users, with real time information about all user access.

The cons of stateful authentication are the pros of stateless authentication and vice versa. For stateless, token revocation is the major issue: once a JWT is issued there is no way of revoking access, whether because the account got compromised and the user contacted support, or for other reasons. The only option apart from waiting for expiry is changing the secret key of the JWT, which will make all users of that platform log out — a serious inconvenience.

The **hybrid approach** beats the cons of both: the main web app running in a browser uses **stateful** authentication, while clients that use the APIs — mobile apps or third party integrations between different servers — adopt **stateless** authentication for scalability and simplicity.

***

## API Key Based Authentication

API key based authentication caters to a completely different set of use cases as compared to stateful and stateless authentication.

How it works: go to a platform that has access, click **generate an API key** in the UI, and get a cryptographically random, cryptographically safe string. That string is used to gain access to the server which caters to this UI.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-89de864df46072b31d65fdcb81233aca618c7b89%2F08-api-key-based-authentication.png?alt=media)

Everyone is familiar with the interface of ChatGPT: something typed in the search bar produces a response — the UI. Behind that UI there are many servers in the cloud responsible for sending the response back to the client, which renders it in a user friendly way.

OpenAI also has a feature called **API keys**, for people who do not want to use the ChatGPT interface but have different use cases of the model — GPT-4, GPT-3.5. They want **programmatic access** to the server: a different UI for their own use case, or the response inside their own server. To provide access to a server, not to a UI or a platform, to a different set of users in a confined manner — permission based and expiry based — people came up with API keys.

* **Easy to generate** -- go to the UI, click generate, and use the key to programmatically access the server.
* **Ideal for machine to machine communication** -- what is normally used is client to server communication: a UI, mouse and keyboard, visual interaction where the UI sends programmatic requests to the server. Machine to machine interaction has no UI and no human interaction. A server with its own UI receives "summarize this paragraph" from a user, and wants to use the existing capabilities of the ChatGPT server to summarize it. That server uses the API key generated from the ChatGPT platform interface to make a request to the ChatGPT server, which identifies the identity, the plan, the quota remaining, the access — all maintained server side using the API key.

Stateful and stateless methods need visual triggers and human triggers: a login form where the username and password are typed, server validation, a JWT token to save. Those complex interactions require human interference, which makes them client to server interactions. In machine to machine interaction the simple workflow is: provide the machine a **secret key**, it saves that in an environment variable or some kind of secure storage, and sends it in all requests to identify itself — instead of the username password token workflow.

***

## OAuth and OpenID Connect

### The Delegation Problem

The setup until now was simple: create an account with username and password, authenticate each time, get a token. The more platforms and sites visited, the more credentials to remember or maintain.

* **Security risk** -- reusing passwords was very common in the initial era, things like 12345 or the string "password", and a single breach compromised a lot of accounts.
* **Fatigue** -- a huge number of accounts to manage was overwhelming for a single user.

Then a class of problem appeared: one website needing access to other websites' resources. A **travel app** — a hotel booking or train booking app — needed access to Gmail to programmatically scan flight tickets. A **social media app** wanted to import contacts from another social media platform or from Google contacts. One platform wanting to import a resource from another platform programmatically.

The initial solution was **sharing passwords**, and this was disastrous:

* Sharing a password meant giving full access to everything in the account, with no way to limit permissions.
* It was impossible to revoke access without changing the password everywhere, since the same passwords were used everywhere. Providing a password to one platform meant it had access to your password and could do anything — a huge security issue.

That is the **delegation problem**: providing resources of one platform to another platform, programmatically, by a user.

In **2007** the birth of **OAuth** happened — open authorization. It was not just a protocol but a revolutionary idea. A couple of engineers from companies like Google or Twitter started working on standardizing a way for users to share access from one platform to another **without sharing passwords**. The technique was **sharing tokens to share access**.

A password has all the access to a user account, while a token has specific access to a specific part of it. If a Google password is shared, everything can be done: view Google photos, see Google Maps history, look at all contacts, add to and delete the calendar. If a token is shared with the capability to **read contacts only** — not delete, not add — then only that specific part of that specific platform can be accessed.

### OAuth 1.0 Components and Flow

Four components:

* **Resource owner** -- the user who owns the data.
* **Client** -- the app requesting access, for example Facebook requesting access to contacts from a Google account.
* **Resource server** -- the server holding the resource, the Google account and Google server.
* **Authorization server** -- the server that issues the token for authenticating the user, after authenticating the user.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-b1a6f067dd9e9b58a1506b28b44464768349beaf%2F09-oauth-1-0-components-and-flow.png?alt=media)

1. The client redirects the user to the authorization server — Facebook redirects the user.
2. The user authenticates and grants permission, accepting the set of permissions asked for by that server.
3. The authorization server sends the token to the client — Facebook.
4. The client uses the token to access the resources — the contacts on the Google server.

OAuth 1.0 solved the problem of sharing passwords to share access in a very beautiful way.

### OAuth 2.0

OAuth 1.0 was pretty revolutionary and useful, but had limitations:

* It was very complex for developers to implement — the whole flow was pretty complicated.
* It used cryptographic signatures, which were very error prone.

OAuth 2.0, which came around **2010**, did two things:

* It introduced **bearer tokens**, which are much simpler in the whole OAuth workflow. It is more vulnerable, but it made implementations more simple.
* It allowed developers to choose **flows based on the app type**:

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-efaff76de8bccda706eb06cab4cfdeebbd2ffd11%2F10-oauth-2-0.png?alt=media)

* **Authorization code flow** -- for server side apps.
* **Implicit flow** -- for browser based apps, now discouraged due to security risks.
* **Client credentials flow** -- for machine to machine communication, like communication between two servers without any involvement of user, browser or human interaction.
* **Device code flow** -- for devices with limited input, no keyboards and mouse, for example a smart TV.

OAuth was great for **authorization**, but it did not solve the issue of **authentication**. Authentication is providing an identity, saying who the user is; authorization is saying what the user can do, what permissions and capabilities exist in the platform. OAuth solved authorization using delegation, but did not solve authentication.

### OpenID Connect

In **2014** or so, people came up with **OpenID Connect**, simply called **OIDC**, built on top of OAuth 2.0's security mechanisms to fill the gap of authentication in the whole authorization workflow.

OIDC extended OAuth 2.0 by introducing an **ID token** — typically a **JWT**. It had information like the user ID, when they logged in through the **iat** field, the issuing authority, the user's name and the user's email — all the information the needing party wants from the other platform.

Because of OpenID Connect, major platforms now offer **sign in with Google, sign in with Facebook, sign in with Discord**. Those features use OpenID Connect behind the scenes to take the identity — email, Google profile picture, name — from Google and store it in their database, or just use it to identify and authenticate the user without having its own authentication mechanisms.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-458f067cf71653236cb2a707319094a18622c3ee%2F11-openid-connect.png?alt=media)

1. The client — a note taking app — redirects the user to the authorization server. The user logs in from **Google's authorization server**, not from the client's server, and grants the permissions asked by the client: reading the user's email, name, profile picture.
2. The authorization server sends two things back to the client: an **authorization code** and an **ID token**.
3. The client uses the authorization code and exchanges it with the resource server for an **access token** on behalf of the user. The ID token is optional at this stage in some workflows — if it was not received in the first step, it is received now, and it is a JWT with the user's ID and name.
4. Using the access token, the note taking server can perform operations on behalf of the user in the resource server. If it asked for Google Keep access during the authorization workflow and the user granted it, it can store that access token and get all the notes from the Google server on behalf of the user — only the stuff it has permission for, nothing else.

OAuth 2.0 and OpenID Connect together act as security guards and key makers of the digital modern age: no user or platform gets more access than they need or asked for in the first place. Together these two revolutionary technologies transformed the internet from password sharing chaos into a secure modern interconnected system, making it possible to integrate one system into another and share resources and permissions.

***

## When to Use Which Type of Authentication

| Type                             | Thumb rule                                                                                                                                                                                |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Stateful authentication**      | Web app based authentication workflows — a large portion of SaaS models — where user specific session data is stored on the server. Uses a session ID or a JWT plus a persistent storage. |
| **Stateless authentication**     | APIs or scalable systems with distributed servers all over the world where tokens carry user info.                                                                                        |
| **OAuth authentication**         | Third party integrations and login via external providers like Google, Facebook, Discord.                                                                                                 |
| **API key based authentication** | Server to server or machine to machine communication, or single purpose client access to APIs.                                                                                            |

Stateful and stateless authentication will be used most of the time while building APIs.

***

## Authorization

Authentication is the **who** — the identity of the user. Authorization is the **permissions** of the user — what a user can do. Those two questions are what make the difference clear.

### The Need for Authorization

Consider a note taking platform where a user has logged in with the appropriate credentials. The user can create notes, delete notes, update notes — the typical function of a note taking platform.

As the creator of that platform, the delete feature does not delete the note permanently. It stores the note in a recycle bin or trash can: the user can either delete it permanently, or it gets automatically deleted after **30 days**. After 30 days all these notes move into another zone called the **dead zone** — deleted for the user, but not permanently deleted from the database.

The creator now wants to inspect these notes programmatically using another UI — an **admin UI** or **creator UI**. That requires permissions which are not granted to every user of the platform.

One solution: come up with a random string and send it with each API, so the server identifies the request as coming from the creator and gives access to those capabilities. The problems:

* **Security flaws** -- if someone intercepts this special "god mode" string, they can do disastrous things: entirely clean the database, mess with other users' data.
* **Management complexity** -- if the same special access has to be given to friends or people who regularly maintain the platform, either that string is handed over or more strings are created with server support for each. The system just got more complicated than before, very difficult to manage and more prone to security flaws.

Providing **specific permissions to specific users** on a platform is basically called **authorization**. Not all users in the platform have the same level of access: some users have different capabilities, some have more, some have less, some have a completely different set of capabilities than other users.

In a **multi-tenant architecture** where users create organizations, the admin needs to assign read or write permission to some users and only read permission to others.

### RBAC — Role Based Access Control

**RBAC** stands for **role based access control**, one of the most famous techniques still used in the modern age that a backend engineer should be familiar with.

A typical platform has different roles: a **user** role, an **admin** role, a **moderator** role. Different roles are assigned different sets of permissions:

* **User** -- assigned read permission.
* **Admin** -- assigned read and write permission.
* **Moderator** -- assigned read and write permission.

Custom roles can be created with their own set of permissions on different kinds of resources. On notes, users might have read, write and delete; on a different resource — accessing the dead zone notes — that permission is only provided to admin. It can be as granular as needed.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-4f03b7a2df6229853a7ce2e75a06251cd5da7ed1%2F12-rbac-role-based-access-control.png?alt=media)

Workflow:

1. A user registers or signs up and the server assigns a role — a user role, or depending on the use case an admin role.
2. In subsequent requests the user sends its token: a session ID using stateful authentication, or a JWT using stateless authentication — whatever the means of identification is.
3. The server deduces the role of the user using that token, or does a database lookup to see the assigned role.
4. The role is deduced early in the request cycle and passed on to the next set of **middlewares**, so the next set of logic has access to this information and can decide whether a user has access to the dead zone notes.
5. If the role is admin, the user has access to the dead zone notes. If the role is user, the API will fail and the server sends an error called **forbidden** with status **403**, which means there is not enough permission to perform this task or access this resource.

***

## Error Messages in Authentication

During the course of authentication there will be instances where messages are sent to the client:

* The user logs in with a certain email that could not be found in the database → **"user not found"**.
* The email was found but the password was incorrect → **"incorrect password"**.
* Invalid credentials tried a few times → **"account locked due to too many failed attempts"**.

While these messages are helpful for legitimate users, they also provide attackers with clues:

* If the error indicates "user not found", the attacker learns that the username does not exist and moves on to the next user in their list.
* If the message is "incorrect password", the attacker knows the username was correct and only has to breach the password — a list of brute force passwords or dictionary attacks follows, because the attack surface increased with that confirmation.

A backend engineer should be aware: **never send specific messages** when it is about authentication. Always keep any message related to authentication **generic** — "authentication failed" for all three cases, no matter what kind of authentication failure happens. The attacker stays confused about the next set of actions to take to breach the system.

User friendly error messages can be sent during other workflows like validations or other APIs — not in this particular workflow.

***

## Timing Attacks

In the security world there is another kind of attack which, even though it is rare, is called a **timing attack**.

In a typical authentication workflow when the user submits their credentials — the email and the password — three things happen in order:

1. **Find user** -- the server verifies the username or email, checking whether it exists in the platform.
2. **Check lock** -- if it exists, the server checks whether the account is locked or suspended for some time — 2 minutes, 4 minutes, a day — because of a couple of invalid credentials tried in the near past.
3. **Check password** -- the provided password is compared with the stored hash. Passwords are not stored in plain text: at sign up the password is taken, hashed into a cryptographically safe string and stored in the database. Even the server cannot get the plain text value of that password. To check it, the provided password is hashed with the same algorithm and key, and that version is compared with the stored hashed version in the database. If they are not the same, the password is incorrect.

![](https://104682095-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FD4zj4siQdcnWP4VjhGQh%2Fuploads%2Fgit-blob-1b4b79de7f0e2b16485be10dbb42f6693d22fbb2%2F13-timing-attacks.png?alt=media)

The problem: if the username is invalid, the system terminates at the first step and the response comes back faster. If the username is valid but the password is incorrect, the system fails at the third step and takes a little longer due to the additional step of hashing the password — hashing takes a little longer than the other operations.

Using that delay, whatever 200 milliseconds or whatever the delay is, attackers can find out whether the username was correct, or at what step the server failed to authenticate. They can decide whether to target the username or to target the password, based on brute force or dictionary attacks.

Two ways to defend against these timing attacks, making sure authentication systems **equalize the response times**:

1. **Constant time operations** -- there are cryptographically secure constant time comparison functions for password hashes. These functions ensure that execution time does not vary based on the input similarity.
2. **Simulate a response delay** -- using whatever programming language is in use: `setTimeout` in Node, or `time.Sleep` in Go. Even in the case of a username not matching, the system does not send a response immediately — it simulates some kind of delay like 200 milliseconds and then sends the response, so attackers cannot measure the timing differences between a username failing and a password failing.

These are the two major kinds of security related information to keep in mind while implementing authentication and authorization.

***

## Resource

* [8. Authentication and authorization for backend engineers -- Sriniously](https://youtu.be/A95rliroC8Q?si=1gh79IIsAwgovs94)
