Basic & Bearer Authentication

Basic & Bearer Authentication

Once your connection is encrypted with HTTPS, you need a way to prove who you are to the server. Authentication is how a server confirms your identity before granting access to protected resources.


1. Authentication vs Authorisation

These terms are often confused:

ConceptQuestion answeredExample
AuthenticationWho are you?"I'm Alice" — proving identity
AuthorisationWhat can you do?"Alice can read, but not delete posts" — permission check

You must be authenticated before you can be authorised.


2. The Authorization Header

HTTP carries authentication credentials in the Authorization request header:

Authorization: [Scheme] [Credentials]
       │              │          │
       │              │          └── The encoded credentials
       │              └───────────── The method (e.g. Basic, Bearer, Digest, AWS4-HMAC-SHA256)
       └──────────────────────────── The header name

3. Basic Authentication

HTTP Basic Auth is the simplest scheme. The client sends a username and password with every request.

Encoding

The credentials are Base64-encoded (not encrypted!):

username:password
    ↓ Base64 encode
dXNlcm5hbWU6cGFzc3dvcmQ=

Request header:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

⚠️ Base64 is encoding, not encryption. Anyone who intercepts the header can decode it in one step. Basic Auth is only safe over HTTPS. Never use it over plain HTTP.

How Basic Auth Works

Client                              Server
  │── GET /protected HTTP/1.1 ────►│
  │                                │
  │◄── 401 Unauthorized ───────────│
  │    WWW-Authenticate: Basic realm="Nandhoo API"
  │
  │  User enters username + password
  │  Browser base64 encodes "alice:password123"
  │
  │── GET /protected HTTP/1.1 ────►│
  │   Authorization: Basic YWxpY2U6cGFzc3dvcmQxMjM=
  │                                │
  │◄── 200 OK ──────────────────── │

Using Basic Auth in JavaScript

const username = 'alice';
const password = 'password123';

// Manually encode credentials
const credentials = btoa(`${username}:${password}`);

const response = await fetch('https://api.nandhoo.com/protected', {
  headers: {
    'Authorization': `Basic ${credentials}`
  }
});

// Or using fetch's built-in approach via a URL (not recommended for security)
// Never put credentials in URLs in production! They appear in logs.

When to Use Basic Auth

✅ Acceptable❌ Not Acceptable
Internal APIs between backend servicesUser-facing login flows
CI/CD systems with service accountsAny HTTP (non-HTTPS) connection
Webhook endpointsAnywhere the credentials persist long-term
Quick local development testing

4. Bearer Token Authentication

Bearer Auth uses tokens instead of credentials. You authenticate once (with your password) to get a token, then use the token for all subsequent requests.

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Bearer Token vs Basic Auth

Basic Auth (every request):
  Client ──── username:password ──────────────────────────────────────► Server
              Risky — password travels in every request

Bearer Auth (token-based):
  Step 1 — Login:
    Client ──── username:password ─────────────────────────────────────► Server
    Client ◄─── access_token, refresh_token ───────────────────────────  Server

  Step 2 — Use API (password NEVER sent again):
    Client ──── Bearer access_token ───────────────────────────────────► Server
    Client ◄─── Protected resource ────────────────────────────────────  Server

5. JWT — JSON Web Tokens

The most common type of bearer token is a JWT (JSON Web Token).

Structure

A JWT has three Base64URL-encoded parts separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiI0MiIsIm5hbWUiOiJBbGljZSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxMTg2NDI4MCwiZXhwIjoxNzExOTUwNjgwfQ
.
X5kLmQ3Rn8Ps7tH2MvJz4Yw9Dk1Nc6Ua0Fe5Bi3Go

Part 1 — Header (algorithm and type):
{
  "alg": "HS256",   // Signing algorithm
  "typ": "JWT"      // Token type
}

Part 2 — Payload (claims):
{
  "sub":  "42",         // Subject — user ID
  "name": "Alice",      // Custom claim
  "role": "admin",      // Custom claim
  "iat":  1711864280,   // Issued At (Unix timestamp)
  "exp":  1711950680    // Expiration (Unix timestamp, 24h later)
}

Part 3 — Signature:
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  SECRET_KEY
)

Standard JWT Claims

ClaimNameMeaning
subSubjectUser ID — who this token is about
issIssuerWho issued the token (e.g. "nandhoo.com")
audAudienceWho the token is intended for
iatIssued AtUnix timestamp when the token was created
expExpirationUnix timestamp when the token expires
nbfNot BeforeToken is not valid before this time
jtiJWT IDUnique ID for this token (enables blacklisting)

How the Server Validates a JWT

Incoming JWT: header.payload.signature

Server:
  1. Base64URL-decode the header → get algorithm (HS256)
  2. Re-compute expected signature:
       HMACSHA256(header + "." + payload, SECRET_KEY)
  3. Compare with received signature
     - If they match → payload is authentic (not tampered with)
     - If they don't → reject (403 Forbidden)
  4. Check exp claim → reject if token is expired
  5. Read sub, role, etc. from payload → authorise the request

No database lookup needed — the token is self-contained.


6. Access Tokens and Refresh Tokens

Since JWT access tokens can't be easily revoked, they are kept short-lived (minutes to hours). Refresh tokens are long-lived and used only to get new access tokens.

LOGIN FLOW:
  Client ── POST /auth/login (email+password) ──────────────► Server
  Client ◄── {                                                 Server
                access_token:  "eyJ..." (expires: 15 min),
                refresh_token: "dRT..." (expires: 7 days)
              }

USE API (while access token is valid):
  Client ── GET /api/profile (Bearer access_token) ─────────► Server
  Client ◄── 200 OK, user profile data                         Server

ACCESS TOKEN EXPIRES:
  Client ── POST /auth/refresh (Bearer refresh_token) ──────► Server
  Client ◄── { access_token: "eyJ..." (new, expires: 15 min) } Server

LOGOUT (revoke refresh token):
  Client ── POST /auth/logout (Bearer refresh_token) ───────► Server
  Server deletes/blacklists the refresh_token
  Client ◄── 204 No Content                                    Server

7. Full Authentication Flow Code Example

class AuthService {
  constructor(apiBase) {
    this.apiBase = apiBase;
    this.accessToken = null;
  }

  // Step 1 — Login
  async login(email, password) {
    const res = await fetch(`${this.apiBase}/auth/login`, {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify({ email, password })
    });

    if (res.status === 401) throw new Error('Invalid email or password');
    if (!res.ok) throw new Error('Login failed');

    const { access_token, refresh_token } = await res.json();

    // Store in memory (most secure) or httpOnly cookie (better)
    this.accessToken = access_token;
    localStorage.setItem('refresh_token', refresh_token);

    return access_token;
  }

  // Step 2 — Make authenticated requests
  async get(path) {
    if (!this.accessToken) throw new Error('Not logged in');

    const res = await fetch(`${this.apiBase}${path}`, {
      headers: { 'Authorization': `Bearer ${this.accessToken}` }
    });

    // Token expired — try to refresh silently
    if (res.status === 401) {
      await this.refresh();
      return this.get(path); // Retry once
    }

    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  }

  // Step 3 — Refresh access token silently
  async refresh() {
    const refreshToken = localStorage.getItem('refresh_token');
    if (!refreshToken) throw new Error('Session expired. Please log in again.');

    const res = await fetch(`${this.apiBase}/auth/refresh`, {
      method:  'POST',
      headers: { 'Authorization': `Bearer ${refreshToken}` }
    });

    if (!res.ok) {
      this.logout();
      throw new Error('Session expired. Please log in again.');
    }

    const { access_token } = await res.json();
    this.accessToken = access_token;
  }

  // Step 4 — Logout
  async logout() {
    const refreshToken = localStorage.getItem('refresh_token');
    if (refreshToken) {
      await fetch(`${this.apiBase}/auth/logout`, {
        method:  'POST',
        headers: { 'Authorization': `Bearer ${refreshToken}` }
      }).catch(() => {}); // Fire and forget
    }
    this.accessToken = null;
    localStorage.removeItem('refresh_token');
  }
}

// Usage
const auth = new AuthService('https://api.nandhoo.com');

await auth.login('alice@example.com', 'password123');
const profile = await auth.get('/profile');
await auth.logout();

8. OAuth 2.0 — Delegated Access

OAuth 2.0 is the standard for allowing a third-party application to act on your behalf without sharing your password.

"Login with Google" Flow (Authorization Code Grant):

1. User clicks "Login with Google"
   Your App → Redirects to: https://accounts.google.com/o/oauth2/auth
                             ?client_id=YOUR_APP_ID
                             &redirect_uri=https://nandhoo.com/callback
                             &scope=email+profile
                             &response_type=code

2. User logs into Google and grants permission

3. Google redirects back to:
   https://nandhoo.com/callback?code=AUTHORIZATION_CODE

4. Your backend exchanges the code for tokens:
   POST https://oauth2.googleapis.com/token
   {client_id, client_secret, code, redirect_uri}
   → {access_token, id_token, refresh_token}

5. Your backend reads user info from ID token
   → User is logged in!

OAuth 2.0 is used by GitHub, Google, Apple, Twitter, and most social login systems.


9. API Keys

API keys are a simpler form of bearer authentication used by public APIs.

// Common styles of passing API keys:

// 1. Authorization header (preferred)
fetch('https://api.openai.com/v1/chat', {
  headers: { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` }
});

// 2. Custom header
fetch('https://api.service.com/data', {
  headers: { 'X-API-Key': process.env.API_KEY }
});

// 3. Query parameter (avoid — keys appear in logs/browser history)
fetch(`https://api.service.com/data?api_key=${API_KEY}`);
// ❌ Don't do this for sensitive keys!

10. Storing Tokens Securely

Storage LocationXSS RiskCSRF RiskNotes
Memory (JS variable)Low ✅None ✅Lost on page refresh — best for access tokens
sessionStorageHigh ❌None ✅Lost on tab close. XSS can read it
localStorageHigh ❌None ✅Persists across sessions. XSS can read it
HttpOnly cookieNone ✅Medium ⚠️JS can't read it but CSRF must be mitigated

Best practice: Store the access token in memory (JS variable); store the refresh token in an HttpOnly cookie so JavaScript can't steal it.


11. Common Mistakes

MistakeProblemFix
Using Basic Auth over HTTPCredentials visible to anyone intercepting trafficAlways use HTTPS with Basic Auth
Storing JWTs in localStorageXSS can steal tokensStore access token in memory; refresh token in HttpOnly cookie
Long-lived access tokensCompromised token valid for days/weeksKeep access tokens short-lived (15 min)
No token expiry (exp claim)Tokens valid foreverAlways set exp in JWTs
Including passwords in JWTsAnyone can base64-decode the payloadJWTs are encoded, not encrypted — use claims only
Not revoking refresh tokens on logoutOld refresh tokens can get new access tokensDelete/blacklist refresh token on logout

12. Review Questions

  1. What is the difference between authentication and authorisation?
  2. Why is Basic Auth only safe over HTTPS?
  3. What are the three parts of a JWT?
  4. Is the JWT payload encrypted? What does Base64URL encoding do?
  5. Why are access tokens kept short-lived, and what is the role of a refresh token?
  6. What is OAuth 2.0 and when would you use it instead of your own auth?
  7. Where should you store a JWT refresh token for maximum security?