All posts

authentication guide

authentication guide

authentication guide

Authentication Guide

Authentication is one of the foundational concepts in software security, but the surrounding terminology can easily become confusing: Session, Cookie, Basic Auth, API Key, Bearer Token, JWT, OAuth, OpenID Connect, Access Token, Refresh Token, and MFA.

The main source of confusion is that these terms do not all describe the same kind of thing, and they do not even operate at the same layer. For example, JWT is a token format, Bearer describes how a token is presented, and OAuth is an authorization framework.

The best way to understand all of this is to start from the basics.


Authentication vs. Authorization

Before anything else, we need to separate two important concepts.

Authentication answers:

Who are you?

Authorization answers:

Now that I know who you are, what are you allowed to do?

For example, when you log in to a website using a username and password, the system is performing authentication.

When the system checks whether that user is allowed to access the admin panel, delete an order, or view another user's information, that is authorization.

In simple terms:

Authentication
      ↓
Who are you?

Authorization
      ↓
What are you allowed to do?

In most systems, authentication happens first, and authorization is then performed based on the authenticated identity.


Username and Password

The most familiar authentication method is username and password.

The user enters credentials such as:

Username: ali
Password: MySecret123

The server verifies whether those credentials are valid.

If they are valid, authentication succeeds and the system knows who the user is.

However, there is one extremely important rule:

Passwords must never be stored in plain text.

This is wrong:

ali | hello123

If the database is leaked, every user's password is immediately exposed.

Instead, the password should be processed using a password hashing algorithm such as Argon2 or bcrypt.

Conceptually:

hello123
    ↓
Password Hashing
    ↓
$argon2id$v=19$...

The resulting hash is stored in the database.

When the user logs in later, the server verifies the provided password against the stored hash.


Hashing Is Not Encryption

These two concepts should not be confused.

Encryption is generally reversible:

Data
 ↓
Encryption
 ↓
Encrypted Data
 ↓
Decryption
 ↓
Data

Password hashing is designed to be one-way:

Password
   ↓
Hash

The server is not supposed to decode the hash and recover the original password.

Passwords are normally stored using password-specific hashing algorithms together with a salt. These algorithms are intentionally relatively expensive to calculate so that password-guessing attacks become more difficult.


The Problem with Passwords

A large part of the password problem comes from user behavior.

Users often choose passwords such as:

123456
password
qwerty

They may also reuse the same password across several websites.

If credentials from one website are leaked, an attacker may try the same username and password on other services.

This type of attack is called Credential Stuffing.

For this reason, passwords alone are not always enough, especially for sensitive systems. This is why MFA is often added as another layer.


Session and Cookie

Suppose the user has successfully entered their username and password and logged in.

A very bad approach would be to send the username and password again with every request:

GET /profile
username=ali
password=123456

Then again:

GET /orders
username=ali
password=123456

And again:

GET /messages
username=ali
password=123456

To avoid this, one traditional and very common solution is a Session.

After a successful login, the server creates a session.

For example:

Session ID:

abc98df712

The server may store something like:

abc98df712 → user_id = 42

The server now knows which user belongs to that session identifier.


A cookie is a browser mechanism for storing small pieces of data and automatically sending them with matching requests.

The server may return:

Set-Cookie: sessionId=abc98df712

The browser stores that cookie.

On future requests, the browser can automatically send:

Cookie: sessionId=abc98df712

The server receives the session ID and looks it up in its session store:

abc98df712
     ↓
user_id = 42
     ↓
Ali

The server therefore knows that the request belongs to Ali.

The complete flow looks roughly like this:

User
 │
 │ Username + Password
 ▼
Server
 │
 │ Credentials are valid
 ▼
Create Session
 │
 │ Session ID = abc123
 ▼
Browser
 │
 │ Stores Session ID in Cookie
 ▼
Future Requests

This distinction is important.

A session usually represents authentication state stored on the server.

For example:

abc123:
{
    userId: 42,
    role: "admin"
}

A cookie exists in the browser and may simply contain the session identifier:

sessionId=abc123

So, in simple terms:

Session → Server-side state

Cookie → Browser mechanism

Cookies can also be used for many things unrelated to authentication.


Logout in Session-Based Authentication

The server can invalidate the session:

delete session abc123

Now, even if the browser sends:

Cookie: sessionId=abc123

the server will no longer find a valid session.

The user is therefore no longer authenticated.

This is one of the useful properties of server-side sessions: the server has direct control and can invalidate a session immediately.


Sessions in Large Systems

If a system has millions of users, managing a large number of sessions can become more complicated.

For that reason, distributed applications often store session data in shared systems such as Redis, allowing multiple backend servers to access the same session store.


Basic Authentication

Basic Authentication is one of the simplest authentication methods supported by HTTP.

The client first combines the username and password:

ali:123456

It then encodes the value using Base64:

YWxpOjEyMzQ1Ng==

And sends it in the HTTP Authorization header:

Authorization: Basic YWxpOjEyMzQ1Ng==

Base64 Is Not Encryption

This is extremely important.

Base64 is an encoding format, not encryption.

If someone has:

YWxpOjEyMzQ1Ng==

they can easily decode it back into:

ali:123456

Therefore:

Base64 ≠ Encryption

Basic Authentication must be used over HTTPS.

HTTPS protects the credentials while they are being transmitted. Base64 itself provides no confidentiality.


Basic Auth vs. Session Authentication

With session-based authentication, a user usually logs in once:

Login
 ↓
Session ID
 ↓
Future Requests

With Basic Authentication, credentials are typically sent on every request:

Request 1 → Username + Password
Request 2 → Username + Password
Request 3 → Username + Password

Because of this, Basic Auth is more commonly used for simple APIs, internal tools, development environments, or limited scenarios, rather than as the first choice for a modern public web application.


API Key

Sometimes the client is not a human user.

An application or service may want to call an API.

Suppose we have a weather API.

Each application may receive an API key:

sk_abc123xyz

The client sends it with a request:

GET /weather

x-api-key: sk_abc123xyz

The server verifies the API key and determines which client or project is making the request.

For example:

sk_abc123xyz
      ↓
Company ABC

What Are API Keys Used For?

API keys can be used for things such as:

API Access
Rate Limiting
Billing
Usage Tracking

For example:

API Key X
→ 10,000 Requests / Month

API Keys Usually Identify an Application

An important point is that an API key does not necessarily identify a human user.

It may simply identify:

API Key
   ↓
MyWeatherApp

The server may still have no idea whether the current user is Ali or Reza.

So API keys and user authentication are not necessarily the same thing.


An API Key Is a Secret

If an API key is leaked, an attacker may be able to use the API as that client.

Secret API keys should therefore not be placed in public repositories, frontend JavaScript, or other places where users can see them.

An API key should be protected like any other sensitive credential.


Token-Based Authentication

In a token-based model, the user first authenticates.

For example:

Username + Password

The server verifies the credentials and, if valid, issues a token:

token = abc123xyz

From this point onward, the client no longer needs to send the password with each request.

Instead, it sends the token.

For example:

Authorization: Bearer abc123xyz

What Is a Token?

A token is essentially a credential that a client can use to prove that it has been granted some form of access.

A token may simply be a random string:

2f7c9d885937a...

This is often called an Opaque Token.

Or it may be structured, such as a JWT:

eyJhbGciOi...

So a token is not necessarily a JWT.

A useful sentence to remember is:

A JWT can be used as a token, but not every token is a JWT.


What Is a Bearer Token?

Bearer should not be confused with JWT.

Bearer is not a specific token format.

It mainly describes how a credential is presented.

For example:

Authorization: Bearer <token>

The idea behind a Bearer token is roughly:

Whoever possesses the token can use its authority.

A simple analogy is a movie ticket.

If you possess a valid ticket:

Ticket
  ↓
Entry

the ticket itself is what grants access.

Bearer tokens behave similarly.

If a Bearer token is stolen, an attacker may be able to use it until the token expires or is revoked.

That is why Bearer tokens must be protected carefully.


Access Token

A token that a client uses to access an API or Resource Server is usually called an Access Token.

For example:

GET /profile

Authorization: Bearer ACCESS_TOKEN

Access tokens usually have a relatively short lifetime.

For example:

15 Minutes

If an access token is stolen, the attacker can only use that same token until it expires.

For example:

12:00 → Token stolen
12:15 → Token expires

A short lifetime is therefore a form of damage limitation.


Refresh Token

If an access token expires quickly, we do not want the user to re-enter a username and password every few minutes.

This is where the Refresh Token comes in.

The client can send the refresh token to the Authorization Server:

Refresh Token
      ↓
Authorization Server
      ↓
New Access Token

The user can therefore maintain a longer-lived login session while the access tokens used against APIs remain short-lived.


Can a Refresh Token Be Stolen?

Yes.

A refresh token can also be stolen, and in many cases a stolen refresh token is even more dangerous than a stolen access token.

An attacker with a stolen access token is usually limited by that token's expiration time.

An attacker with a valid refresh token may be able to request new access tokens.

For example:

Stolen Refresh Token
        ↓
Authorization Server
        ↓
New Access Token

So the purpose of refresh tokens is not to create a credential that cannot be stolen.

The goal is to keep the more sensitive credential less exposed and better protected.


Why Is the Access Token Short-Lived but the Refresh Token Longer-Lived?

The access token may be used in a large number of requests:

GET /profile
Authorization: Bearer ACCESS_TOKEN
GET /orders
Authorization: Bearer ACCESS_TOKEN
POST /comments
Authorization: Bearer ACCESS_TOKEN

It lives on a high-traffic path.

A refresh token, in a well-designed system, is used only when a new access token is needed.

For example:

Client
   │
   │ Refresh Token
   ▼
Authorization Server
   │
   │ New Access Token
   ▼
Client

So ideally:

Access Token
→ Frequent usage
→ Greater exposure
→ Short lifetime

Refresh Token
→ Limited usage
→ Stronger protection
→ Longer lifetime

What If Both Access and Refresh Tokens Are Stored in Cookies?

This is a very important question.

Suppose both tokens are stored like this:

access_token=AAA;
HttpOnly;
Secure;
Path=/

refresh_token=RRR;
HttpOnly;
Secure;
Path=/

The browser may then send both cookies with ordinary API requests.

For example:

GET /api/profile

and the browser might send:

Cookie:
access_token=AAA;
refresh_token=RRR;

Then:

GET /api/products

and again both tokens are sent.

In this design, one of the main benefits of separating access tokens from refresh tokens is weakened, because the refresh token is unnecessarily exposed to every backend request.


A Better Cookie Design

Cookie scope can be restricted.

For example:

Set-Cookie: access_token=AAA;
            HttpOnly;
            Secure;
            Path=/api

And:

Set-Cookie: refresh_token=RRR;
            HttpOnly;
            Secure;
            Path=/auth/refresh

Now when the browser sends:

GET /api/profile

the access token may be sent to the API path.

But the refresh token is not in scope for ordinary API endpoints.

When the access token expires, the client can call:

POST /auth/refresh

and the refresh token is used only there.

Architecturally:

Access Token
     ↓
Resource Server / API

Refresh Token
     ↓
Authorization Server / Refresh Endpoint

This difference is often more important than simply giving the two tokens different expiration times.


What Does HttpOnly Actually Do?

You often hear that authentication tokens should be placed inside HttpOnly cookies.

But it is important to understand exactly what HttpOnly means.

HttpOnly does not mean the cookie will not be sent to the server.

The browser still sends the cookie automatically with matching requests.

HttpOnly only prevents normal browser JavaScript from directly reading the cookie value.

For example, JavaScript should not be able to simply retrieve it through:

document.cookie

So:

HttpOnly
    ↓
Prevents normal JavaScript access to Cookie value

What Does Secure Do?

If a cookie has the:

Secure

attribute, the browser sends it only over HTTPS.

So:

Secure
   ↓
Cookie only over HTTPS

What Does SameSite Do?

SameSite controls how cookies behave in cross-site requests.

It is one of the mechanisms that can help reduce certain forms of CSRF attacks.

Conceptually:

SameSite
    ↓
Controls cross-site cookie behavior

What Do Path and Domain Do?

Path determines which URL paths the cookie can be sent to.

Domain determines which hosts or domains the cookie is valid for.

So:

HttpOnly
→ JavaScript access

Secure
→ HTTPS only

SameSite
→ Cross-site behavior

Path / Domain
→ Where the Cookie is sent

Each property solves a different security problem.


Access Tokens and Refresh Tokens Do Not Have the Same Authority

Even if both are tokens, they serve different purposes.

The access token is used against a Resource Server:

Access Token
     ↓
API
     ↓
GET /orders
POST /comments
GET /profile

The refresh token should not be accepted as a normal API credential.

For example, this should not work:

GET /orders

Authorization: Bearer REFRESH_TOKEN

The Resource Server should reject it.

A refresh token exists only to obtain new access tokens:

Refresh Token
      ↓
Authorization Server
      ↓
Access Token

So the difference is not only:

Access Token → 15 minutes
Refresh Token → several days

Their audience and intended usage are also different.


Refresh Token Rotation

One important technique for improving refresh token security is Refresh Token Rotation.

Suppose the client initially has:

RT1

The client uses it:

RT1
 ↓
Authorization Server
 ↓
Access Token + RT2

The server invalidates RT1.

Now:

RT1 → Invalid
RT2 → Valid

Next time:

RT2
 ↓
Authorization Server
 ↓
Access Token + RT3

Then:

RT2 → Invalid
RT3 → Valid

Each refresh token is therefore intended to be used only once.

If an old token such as RT1 later appears again:

Attacker
   ↓
RT1
   ↓
Server
   ↓
This token was already used

that may indicate token theft.

The server may then revoke the session or the related family of refresh tokens.

These ideas are commonly referred to as Refresh Token Rotation and Reuse Detection.


What If an Attacker Steals Both Tokens?

That is a serious situation.

Access and refresh tokens are not magical security mechanisms.

Security is usually about reducing the probability of compromise and limiting the impact when something goes wrong.

If an attacker obtains both credentials, they may be able to use the current access token and also request new ones using the refresh token.

That is why refresh tokens normally receive stronger protection.

Typical protections include:

Secure Storage
Limited lifetime
Rotation
Revocation
Reuse Detection
Appropriate Cookie scope

BFF: What If the Browser Stores No Tokens at All?

Some web applications use another architecture.

Instead of storing access and refresh tokens directly in the browser, the backend stores them.

The browser only holds a session cookie.

For example:

Browser
   │
   │ Session Cookie
   ▼
Backend / BFF
   │
   ├── Access Token
   └── Refresh Token
   │
   ▼
APIs / Authorization Server

In this model:

Browser
❌ Access Token
❌ Refresh Token

Backend
✅ Access Token
✅ Refresh Token

This type of pattern is commonly called a Backend for Frontend, or BFF.

There is no rule saying that access tokens and refresh tokens must always be stored directly inside browser cookies.


What Is JWT?

JWT stands for:

JSON Web Token

JWT is not an authentication protocol.

It is a standardized token format.

This distinction is important.

JWT ≠ Authentication Protocol

JWT = Token Format

JWT Structure

A JWT usually contains three parts:

xxxxx.yyyyy.zzzzz

That means:

Header.Payload.Signature

For example:

eyJhbGciOiJIUzI1NiJ9
.
eyJzdWIiOiIxMjMifQ
.
SflKxwRJSMeKK...

The header usually contains metadata about the token.

For example:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

The payload contains claims.

For example:

{
  "sub": "12345",
  "name": "Ali",
  "role": "admin",
  "exp": 1770000000
}

Information inside a JWT is commonly called claims.

For example:

sub

usually represents the Subject.

And:

exp

represents the expiration time.


Signature

The signature helps the receiver detect whether the token has been modified without authorization.

Conceptually:

Header
+
Payload
+
Secret / Private Key
       ↓
Signature

Suppose an attacker changes:

"role": "user"

to:

"role": "admin"

If signature validation is performed correctly, the modified token will no longer be valid.


Is JWT Data Secret?

Not necessarily.

This is one of the most common misunderstandings about JWT.

The payload of a normal JWT can usually be decoded.

Therefore, sensitive information should not be considered protected simply because it is inside a JWT.

For example, this would be a bad idea:

{
  "password": "secret",
  "creditCard": "..."
}

A signature primarily protects integrity:

Has the data been modified?

It does not necessarily provide confidentiality:

Can anyone see the data?

In other words:

A signed JWT is not necessarily an encrypted JWT.


What Is the Benefit of JWT?

A JWT can carry useful information inside the token itself.

For example:

{
  "userId": 42,
  "role": "admin"
}

After properly validating the token, a Resource Server may be able to use some of those claims directly.

This is one reason JWTs are common in distributed and API-based systems.


JWT and Logout

Suppose a JWT is valid until 18:00.

The user logs out at 17:00.

If the system is fully stateless, the JWT may still be cryptographically valid until 18:00.

For example:

17:00 → User logs out

JWT
 ↓
Still valid until 18:00

This can make revocation more complicated than with fully server-side sessions.

Possible approaches include:

Short-lived Access Tokens
Revocation mechanisms
Token deny lists in some architectures
Refresh Token revocation
Session tracking

So statelessness is not automatically better. It introduces trade-offs.


What Is OAuth?

OAuth is one of the most misunderstood terms in authentication-related discussions.

OAuth is primarily designed for Delegated Authorization.

In other words:

How can we allow an application to access a limited set of our resources without giving that application our password?


The Password-Sharing Problem

Suppose a photo-printing application wants access to your photos.

A terrible design would be:

Google Username?
Google Password?

The user would be giving their full Google credentials directly to the third-party application.

That is dangerous.

OAuth addresses this problem.

Instead of sharing the password, the application receives a limited access token.

Conceptually:

Photo App
   ↓
Authorization Server
   ↓
User gives permission
   ↓
Photo App receives limited access

The application never needs to receive the user's original account password.


Main Roles in OAuth

OAuth has four important roles.

Resource Owner

Usually the user.

For example:

Ali

The user owns or controls access to the resource.


Client

The application requesting access.

For example:

Photo Printing App

Authorization Server

The system that manages authorization and issues tokens.

The user interacts with this server for authentication and consent.


Resource Server

The server that stores or exposes the protected resource.

For example:

Photos API

The Resource Server validates the access token before granting access to the resource.


A Simplified OAuth Flow

The client wants access to the user's photos.

The user is redirected to the Authorization Server.

They may see something like:

PhotoApp wants permission to:

✓ Read your photos

The user clicks:

Allow

In a common flow, the client first receives an Authorization Code.

Then:

Authorization Code
       ↓
Authorization Server
       ↓
Access Token

The client now has an access token and can call the Resource Server:

GET /photos

Authorization: Bearer ACCESS_TOKEN

The Resource Server validates the token and returns the requested resource if access is allowed.


What Is a Scope?

OAuth allows access to be limited.

For example, a client may receive permission to only read photos:

photos.read

but not delete them.

Other scopes might be:

contacts.read
calendar.write
profile.read

A scope describes what type of access a client requests or receives.

This is closely related to a fundamental security principle:

Principle of Least Privilege

Meaning:

Grant only the minimum access that is actually required.


Is OAuth for Login?

OAuth is primarily about authorization.

Its core question is closer to:

What can this application access?

not necessarily:

Who exactly is this user?

For authentication and identity, another layer called OpenID Connect is commonly used.


OpenID Connect, or OIDC

OpenID Connect, usually abbreviated as OIDC, is an identity layer built on top of OAuth.

In very simplified terms:

OAuth
+
Identity Layer
=
OpenID Connect

OIDC is widely used for scenarios such as login and Single Sign-On.


Access Token vs. ID Token

With OIDC, Access Tokens and ID Tokens should be treated as different things.

Access Token

An Access Token is for the API or Resource Server:

Client
   ↓
Access Token
   ↓
API

For example:

Authorization: Bearer ACCESS_TOKEN

ID Token

An ID Token is intended for the client and contains information about authentication and identity.

An ID Token is typically a JWT.

For example:

{
  "sub": "983725",
  "iss": "https://identity.example",
  "aud": "my-app",
  "exp": 1770000000
}

After proper validation, the client can determine which subject was authenticated.


Access Token vs. ID Token

In short:

Access Token
→ API Access

ID Token
→ Authentication / Identity information

They should not be used interchangeably.


How Does “Login with Google” Fit Into This?

When a user clicks something like:

Continue with Google

the application redirects the user to an Identity Provider.

The user authenticates there.

Through a standardized flow, the application receives the information it needs and can establish a local session for the user.

OpenID Connect commonly plays the identity role in this kind of flow.

So, very simply:

OAuth
→ Delegated Authorization

OIDC
→ Authentication + Identity

What Is MFA?

MFA stands for:

Multi-Factor Authentication

The goal of MFA is to avoid relying on a single credential such as a password.

For example:

Password
+
Second Factor

Authentication Factors

Authentication factors are commonly divided into several categories.

Something You Know

Something the user knows:

Password
PIN

Something You Have

Something the user possesses:

Phone
Authenticator Device
Hardware Security Key

Something You Are

A physical or biometric characteristic:

Fingerprint
Face

Is Password + PIN MFA?

Not necessarily.

Both belong to the same factor category:

Password
+
PIN

Both are:

Something You Know

True MFA generally combines independent factors.

For example:

Password
+
Security Key

Here one factor is Something You Know, and the other is Something You Have.


OTP

A common second factor is a One-Time Password.

The user first enters the password.

Then the system asks:

Enter your 6-digit code

An authenticator application might display:

418239

Now, even if an attacker knows the password, they still need the second factor.

SMS can also be used as a second factor, although stronger options such as authenticator applications and hardware security keys also exist.


What Is an RFC?

When discussing authentication and internet protocols, you will often see references such as:

RFC 7519
RFC 6750
RFC 6749

RFC stands for:

Request for Comments

The name can sound informal, but RFCs are very important technical documents used on the internet.

Many internet protocols, formats, and standards are precisely documented through RFCs.

For example:

RFC 7519 → JWT

RFC 6750 → Bearer Token Usage

RFC 6749 → OAuth 2.0

Not every RFC is necessarily a final Internet Standard; RFCs have different categories.

But in practical terms, they can be thought of as official technical documents that precisely define protocol and standard behavior.


What Is the IETF?

Many important RFCs are published through processes associated with the IETF.

IETF stands for:

Internet Engineering Task Force

A simplified view looks like this:

Experts / Working Groups
       ↓
Technical Specification
       ↓
RFC
       ↓
Implementations

These standards allow implementations from different companies and programming languages to work together.

For example, a Java backend and a Go service can both understand the same JWT structure because they follow the same standardized format.


How All of These Concepts Fit Together

Now we can connect all the pieces.

Suppose we have an online store.

The user first enters:

Username + Password

These credentials are used for authentication.

For additional security, the system may require:

Password
+
Authenticator Code

That is MFA.

After authentication, the system may create a session:

Session
   ↓
Session ID
   ↓
Cookie

Or it may use a token-based architecture and issue an Access Token:

Access Token

The client may present that token as a Bearer token:

Authorization: Bearer <token>

The access token itself may be a JWT:

Bearer
  ↓
JWT Access Token

But it does not have to be a JWT; it may also be an opaque token.

If the access token is short-lived, the client may also receive a Refresh Token:

Refresh Token
     ↓
Authorization Server
     ↓
New Access Token

If a third-party application needs limited access to a user's resources, OAuth may be involved.

If the system needs standardized login and identity, OpenID Connect may be involved.

And once authentication succeeds, authorization determines what the user or client is actually allowed to do.


A Useful Mental Model

You can think of many of these concepts like this:

                    Authentication
                          │
            ┌─────────────┼─────────────┐
            │             │             │
        Password         MFA           OIDC
            │
            ▼
       User Verified
            │
      ┌─────┴─────┐
      │           │
   Session       Token
      │           │
   Cookie     ┌───┴─────┐
              │         │
          Opaque       JWT
              │
              ▼
     Authorization: Bearer ...

OAuth can be viewed on another axis:

OAuth
  │
  ▼
Delegated Authorization
  │
  ├── Access Token
  ├── Scope
  └── Third-party Access

Common Misconceptions

There are several common mistakes worth avoiding.

JWT is not a login method.

JWT = Token Format

Bearer and JWT are not the same thing.

Bearer = How a token is presented

JWT = A possible token format

OAuth by itself is not the same thing as login.

OAuth = Delegated Authorization

OIDC adds identity and authentication concepts on top of OAuth.

OIDC = Identity Layer

Cookie and Session are not the same thing.

Cookie = Browser mechanism

Session = Server-side authentication state

HttpOnly does not mean the cookie is not sent.

HttpOnly
→ JavaScript cannot normally read the cookie

Browser
→ Can still send it automatically

A Refresh Token is not inherently impossible to steal.

A refresh token is still a sensitive credential.

The reason for having separate Access and Refresh Tokens is to separate a frequently used, short-lived credential from a more sensitive, less frequently used credential.


Important Security Practices

Choosing JWT, OAuth, or cookies does not automatically make a system secure.

Implementation matters much more than the name of the technology being used.

In general:

  • Always use HTTPS for authentication data and tokens.
  • Never store passwords in plain text.
  • Use appropriate password hashing algorithms.
  • Keep access tokens as short-lived as reasonably possible.
  • Protect refresh tokens more carefully.
  • Do not send refresh tokens to every API endpoint when they are not needed.
  • Use Refresh Token Rotation and Reuse Detection when appropriate.
  • Follow the Principle of Least Privilege for scopes and permissions.
  • Do not store sensitive tokens in unsafe client-side locations.
  • In web applications, configure sensitive cookies appropriately using attributes such as HttpOnly, Secure, and SameSite, depending on the architecture.
  • Avoid overly broad Cookie Path and Domain settings.
  • Use MFA for sensitive accounts.
  • Design logout, revocation, and expiration from the beginning rather than adding them at the end.

Conclusion

If we reduce the whole topic to a few important ideas:

Authentication answers who you are.

Authorization determines what you are allowed to do.

Username and Password are one of the simplest ways to prove identity, but passwords must be stored securely using password hashing.

Session usually stores login state on the server, while a Cookie can store and transmit a session identifier in the browser.

Basic Auth sends username and password through the HTTP Authorization header, and Base64 provides no real encryption.

API Keys are commonly used to identify a client, application, or project.

Token-Based Authentication replaces repeated password transmission with a token.

Bearer describes a common way of presenting a token, where possession of the token is enough to use its authority.

Access Tokens are used to access Resource Servers and APIs.

Refresh Tokens are used to obtain new Access Tokens and should be used in a much more limited and protected way.

JWT is only a token format, and a signed JWT is not necessarily encrypted.

OAuth is primarily designed for delegated authorization.

OpenID Connect adds an identity layer on top of OAuth and is commonly used for authentication and login.

MFA reduces dependence on a single password by requiring multiple independent authentication factors.

And perhaps the most important lesson is:

Authentication security does not come from choosing a technology with the right name. It comes from architecture, secure credential storage, limited exposure, least privilege, expiration, revocation, and correct implementation.