Skip to content
DevRecall
← Roadmap
23

Authentication, OAuth2, OIDC & Authorization

drafted

Session vs JWT, refresh rotation/revocation, OAuth 2.0 flows + PKCE, OIDC, SSO/SAML, passkeys, RBAC vs ABAC vs ReBAC, tenant-scoped roles, policy engines.

0/66 rated · mastery 0%

Bài học

12 bài · ~142 phút đọc. Học theo thứ tự, rồi làm câu hỏi.
  1. 01~15 phút· 4 câuverify

    Session, cookie và API key: chứng minh danh tính trên từng request

    AuthN khác AuthZ ở đâu và vì sao 401/403/404 không thay thế cho nhau; session server-side và cookie flags; session fixation và vòng đời session (login, đổi quyền, logout); chọn session hay JWT cho web app; thiết kế API key cho đối tác. Có demo express-session thật cho fixation và API key hash.

  2. 02~14 phút· 6 câuverify

    JWT: cấu trúc, chữ ký và cách verify an toàn

    JWS là gì và vì sao payload không bí mật; registered claims; HS256 vs RS256 vs ES256/EdDSA với số đo thật; alg none và algorithm confusion chạy thật trên jsonwebtoken 8.5.1, 9.0.3 và jose 6; tấn công qua kid/jku/x5u/jwk; clock skew và leeway; checklist verify cho resource server.

  3. 03~13 phút· 9 câuverify

    Access token, refresh token, rotation và revocation

    Vì sao access token ngắn và refresh token chỉ đi tới authorization server; rotation + reuse detection theo family cài trên Postgres 17 và chạy thật (kể cả race hai tab và trường hợp attacker refresh trước); mặc định thật của Keycloak 26.4; các cách revoke JWT (TTL, denylist jti, token_version, introspection, phantom token); xử lý sự cố reuse và nhân viên bị xoá khỏi tenant.

  4. 04~11 phút· 5 câuverify

    OAuth 2.0: vai trò, scope và các grant type

    OAuth giải quyết bài toán ủy quyền nào; bốn vai trò áp vào ví dụ Google Calendar; scope khác role và permission ra sao; Client Credentials với client secret và private_key_jwt; Device Authorization Grant chạy thật trên Keycloak 26.4; vì sao Implicit và Password grant bị loại theo RFC 9700 và OAuth 2.1 đổi gì.

  5. 05~11 phút· 5 câuverify

    Authorization Code + PKCE từng bước

    Đi từng tham số của Authorization Code flow và nơi mỗi tham số được kiểm tra; PKCE chống code interception/injection (kiểm bằng test vector RFC 7636); state, nonce và PKCE khác nhau ra sao; mix-up attack và tham số iss (RFC 9207); redirect URI so khớp prefix bị khai thác thế nào; mobile redirect và RFC 8252. Toàn bộ flow chạy thật trên Keycloak 26.4.7.

  6. 06~12 phút· 6 câuverify

    OpenID Connect: ID token, discovery và JWKS

    OIDC thêm gì lên OAuth và vì sao login bằng OAuth thuần nguy hiểm; ID token khác access token ở audience và cách dùng; checklist verify ID token; discovery và JWKS đọc từ Google và Keycloak thật; mô phỏng key rotation với jose (cache, refetch khi gặp kid lạ, cooldown 30 giây) và sự cố 401 hàng loạt sau rotation; Sign in with Google: flow nào, kiểm gì, map identity thế nào.

  7. 07~11 phút· 3 câuverify

    Token trong browser: localStorage, cookie, BFF và DPoP

    XSS làm gì được với token ở từng nơi lưu (localStorage, memory, HttpOnly cookie); ba kiến trúc của IETF cho browser app (BFF, token-mediating backend, browser-only); BFF giải quyết và tạo ra vấn đề gì, cài trong Next.js ra sao; sender-constrained token với DPoP (RFC 9449) và mTLS (RFC 8705), có demo DPoP chạy thật với jose: replay, sai URL, token bị đánh cắp.

  8. 08~10 phút· 4 câuverify

    Audience, service-to-service và identity propagation

    Vì sao thiếu kiểm aud làm billing chấp nhận token của analytics (chạy thật), scope so khớp chuỗi con; resource indicators; ba cách truyền danh tính user giữa service (forward token, token exchange RFC 8693 chạy thật trên Keycloak 26.4, JWT nội bộ + mTLS); service identity trên Kubernetes (shared secret, mesh mTLS/SPIFFE, client credentials); identity trong Kafka event.

  9. 09~12 phút· 7 câuverify

    SSO, SAML, logout và federation cho B2B

    SSO hoạt động thế nào và vì sao deprovision không tự huỷ session ở app; SAML 2.0 vs OIDC và các bẫy SAML (signature wrapping, IdP-initiated, replay); ba kiểu logout của OIDC; Keycloak realm-per-tenant vs single realm + Organizations (chạy thật trên 26.4.7); thiết kế login cho B2B SaaS có tenant mang IdP riêng; migrate từ JWT HS256 tự viết sang OIDC; build vs buy identity.

  10. 10~11 phút· 4 câuverify

    MFA, passkeys và account linking

    Các nhóm factor, vì sao SMS OTP và cả TOTP vẫn phishable; WebAuthn/passkey hoạt động thế nào và tại sao chống được phishing (mô phỏng chạy thật bằng node:crypto với clientDataJSON và rpIdHash); synced vs device-bound; rollout passkey cho 5 triệu user; account pre-hijacking trong social login và quy tắc liên kết tài khoản an toàn.

  11. 11~11 phút· 7 câuverify

    Authorization models: RBAC, ABAC, ReBAC và policy engine

    RBAC và vì sao check permission chứ không check tên role; ABAC với điều kiện trên thuộc tính (Cedar 4.13 chạy thật); ReBAC và mô hình Zanzibar với tuple, check, list objects và new enemy problem (OpenFGA 1.21 chạy thật); so sánh OPA, Cedar, Casbin, OpenFGA; authorization cho list/search (policy thành filter, SQL chạy thật trên Postgres 17); PDP/PEP cho 15 microservice.

  12. 12~11 phút· 6 câuverify

    Authorization multi-tenant: role theo membership, tenant context và cache quyền

    Vì sao role phải gắn với membership (user, tenant) chứ không gắn với user; schema có custom role theo tenant và ràng buộc chống gán chéo tenant (chạy thật trên Postgres 17); lỗi refund chéo tenant và cách sửa; tenant context đến từ đâu (token, header, URL) và vì sao luôn kiểm membership; cache quyền có version và invalidation (đo thật); tách staff và customer trong B2B2C.

Tóm tắt & cheat sheet của track

TL;DR

  • AuthN = bạn là ai; AuthZ = bạn được làm gì với resource nào. Phần lớn sự cố thật nằm ở AuthZ (thiếu check object/tenant), không ở login.
  • OAuth 2.0 là framework ủy quyền (client lấy access token để gọi API thay user); OIDC thêm lớp định danh (ID token, userinfo, discovery). Flow chuẩn cho mọi client có user: Authorization Code + PKCE. Implicit và Password grant đã bị loại (RFC 9700, hướng OAuth 2.1).
  • Session vs JWT không phải "cũ vs mới": session dễ revoke, JWT tiện cho phân tán nhưng khó thu hồi → access token ngắn + refresh token rotation + reuse detection.
  • JWT pitfalls kinh điển: tin alg trong header (none, HS/RS confusion), không check aud/iss, dùng decode thay verify, token sống quá lâu, nhét quá nhiều quyền vào token.
  • Authorization models: RBAC (role → permission), ABAC (thuộc tính + điều kiện), ReBAC (quan hệ, Zanzibar). Multi-tenant: role gắn với membership (user, tenant), không gắn với user.

Khái niệm cốt lõi

Khái niệmMột câuVí dụ ngắn
SessionServer lưu state, client giữ session id trong cookie__Host-sid=... → Redis sess:...
JWT (JWS)Token tự chứa claims, ký (không mã hoá)header.payload.signature, payload đọc được bằng base64
Access tokenCredential để gọi resource serverAuthorization: Bearer eyJ..., sống 5–15 phút
Refresh tokenĐổi lấy access token mới, sống lâuRotation: mỗi lần dùng cấp token mới, huỷ token cũ
ID tokenJWT cho client biết user là ai (OIDC)sub, iss, aud=client_id, nonce
PKCERàng buộc code với client tạo ra nócode_challenge = BASE64URL(SHA256(verifier))
ScopeQuyền client được ủy quyềnorders:read
RBACUser → role → permissionstore_manager → order.refund
ABACQuyết định theo thuộc tính user/resource/contextresource.tenant == user.tenant && amount < 5M
ReBACQuyết định theo quan hệ đồ thịuser:alice là editor của folder:x → edit doc:y trong folder
PasskeyCredential WebAuthn (public-key) gắn với originChống phishing, không có shared secret

Cơ chế hoạt động

Authorization Code + PKCE (OIDC)

Refresh token rotation + reuse detection

Quyết định authorization trong một request multi-tenant

Trade-offs

Quyết địnhLựa chọn ALựa chọn BGhi chú
Web sessionServer session (cookie)JWT statelessSession: revoke tức thì; JWT: không cần lookup nhưng revoke khó
Token của SPABFF giữ token, browser chỉ có cookieToken trong JS memoryBFF an toàn nhất; memory + refresh rotation chấp nhận được
Access token formatJWT (verify local)Opaque + introspectionJWT nhanh; opaque revoke tức thì, không lộ claims
Ký JWTAsymmetric (RS256/ES256/EdDSA)Symmetric HS256Nhiều verifier → asymmetric; HS chỉ khi issuer = verifier
Quyền trong tokenNhét role/permission vào claimsTra cứu lúc requestClaims nhanh nhưng stale tới khi hết hạn và token phình to
ModelRBACABAC / ReBACRBAC đủ cho đa số; share/hierarchy → ReBAC; điều kiện động → ABAC
Enterprise SSOOIDCSAML 2.0OIDC cho app mới; SAML vì khách hàng enterprise yêu cầu
PolicyCode trong servicePolicy engine (OPA/Cedar/OpenFGA)Engine khi nhiều service/nhiều team cần một nguồn luật

Pitfalls

  • jwt.decode() thay jwt.verify(); không truyền algorithms allowlist; không check aud/iss.
  • Dùng ID token để gọi API, hoặc chấp nhận access token cấp cho service khác (thiếu aud).
  • Access token sống 24h, không có refresh rotation, không có cách revoke khi user bị xoá khỏi tenant.
  • Lưu refresh token trong localStorage.
  • Role lưu trên bảng users (một role toàn cục) trong khi user thuộc nhiều tenant.
  • Check role ở route nhưng không check resource thuộc tenant nào.
  • Tin email từ IdP làm khoá định danh / auto-link account khi email_verified không đúng.
  • redirect_uri so khớp prefix/wildcard → rò code.
  • Không xử lý JWKS rotation (cache key mãi mãi, hoặc fetch JWKS mỗi request).

Cheat sheet

  • Flows: có user → Auth Code + PKCE; machine-to-machine → Client Credentials; TV/CLI → Device Authorization Grant (RFC 8628). Không dùng Implicit/ROPC.
  • state = chống CSRF ở callback · nonce = gắn ID token với request (chống replay/injection) · PKCE = chống code interception/injection.
  • Verify JWT: allowlist alg → key theo kid từ JWKS của issuer đã cấu hình → signature → iss → aud → exp/nbf (leeway ~60s) → claims nghiệp vụ.
  • Access token 5–15', refresh token rotation + reuse detection + absolute lifetime; revoke family khi reuse.
  • Revoke JWT: TTL ngắn, denylist jti, token_version/sessions table, introspection.
  • Multi-tenant RBAC: memberships(user_id, tenant_id) + membership_roles + role_permissions; check (tenant membership → permission → object).
  • SAML = XML assertion, enterprise legacy; OIDC = JSON/JWT, discovery, mobile-friendly.
  • Passkeys: WebAuthn, phishing-resistant vì bound với RP ID (origin), private key không rời authenticator.

Personal notes (Notion)

Ghi chú cá nhân — có thể sai hoặc lỗi thời, dùng để tham khảo.

Interview Questions

Showing 66 / 66

const [h, p] = token.split('.');
JSON.parse(Buffer.from(p, 'base64url').toString()); // anyone can read the claims
// avoid: if (user.role === 'admin' || user.role === 'manager')
if (!(await authz.can(ctx, 'order.refund'))) throw new ForbiddenError();
curl -X POST https://auth.example.com/oauth2/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials -d scope="inventory:read"
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(new URL('https://idp.example.com/.well-known/jwks.json'));
const { payload } = await jwtVerify(token, JWKS, {
  issuer: 'https://idp.example.com',
  audience: 'orders-api',
  algorithms: ['RS256'],
  clockTolerance: 60,
});
CREATE TABLE memberships (
  id uuid PRIMARY KEY, user_id uuid NOT NULL, tenant_id uuid NOT NULL,
  status text NOT NULL DEFAULT 'active', UNIQUE (user_id, tenant_id), UNIQUE (id, tenant_id)
);
CREATE TABLE roles (id uuid PRIMARY KEY, tenant_id uuid NULL, key text NOT NULL);
CREATE TABLE membership_roles (
  membership_id uuid NOT NULL, tenant_id uuid NOT NULL, role_id uuid NOT NULL REFERENCES roles(id),
  PRIMARY KEY (membership_id, role_id),
  FOREIGN KEY (membership_id, tenant_id) REFERENCES memberships (id, tenant_id)
);
-- app/trigger check: roles.tenant_id IS NULL OR roles.tenant_id = membership_roles.tenant_id
import jwt from 'jsonwebtoken';

export function auth(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  const claims = jwt.decode(token) as any;          // "we verify at the gateway"
  if (!claims || claims.exp * 1000 < Date.now()) return res.sendStatus(401);
  req.user = { id: claims.sub, tenantId: claims.tid, roles: claims.roles };
  next();
}

// elsewhere, the token service
jwt.verify(token, process.env.JWT_KEY!);   // no algorithms option
const { payload } = await jwtVerify(token, JWKS, {
  issuer: 'https://idp.example.com',
  algorithms: ['RS256'],
});
if (!payload.scope?.toString().includes('read')) throw new Forbidden();
app.post('/auth/refresh', async (req, res) => {
  const rt = await db.refreshToken.findUnique({ where: { token: req.body.refreshToken } });
  if (!rt || rt.expiresAt < new Date()) return res.sendStatus(401);
  const newRt = randomToken();
  await db.refreshToken.create({ data: { token: newRt, userId: rt.userId, expiresAt: addDays(new Date(), 30) } });
  res.json({ accessToken: signAccess(rt.userId), refreshToken: newRt });
});
router.post('/orders/:id/refund', requireAuth, requirePermission('order.refund'), async (req, res) => {
  const order = await orders.findById(req.params.id);
  await payments.refund(order.paymentId, order.total);
  res.json({ ok: true });
});

// requirePermission loads roles for the user across ALL memberships
async function userPermissions(userId: string) {
  return db.query(`SELECT p.key FROM membership_roles mr
    JOIN memberships m ON m.id = mr.membership_id
    JOIN role_permissions p ON p.role_id = mr.role_id WHERE m.user_id = $1`, [userId]);
}
function isAllowedRedirect(client: Client, redirectUri: string) {
  return client.redirectUris.some((allowed) => redirectUri.startsWith(allowed));
}
// registered: https://shop.example.com/callback