NANDHOO.

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

  1. User logs into Google and grants permission

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

  1. 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}

  1. 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?