Deepen PracticeOrdered learning track

Auth Observability

Learn React Authentication, Authorization, Identity & Permission/ACL - Part 101

Production auth observability for React applications: login funnel, session refresh, 401/403 rates, redirect loops, permission drift, tenant mismatch, correlation IDs, frontend telemetry, backend traces, security-safe metrics, SLOs, dashboards, and alerting.

13 min read2563 words
PrevNext
Lesson 101130 lesson track72–107 Deepen Practice
#react#authentication#authorization#observability+6 more

Part 101 — Auth Observability

Auth observability is the ability to answer production questions about authentication and authorization without guessing, reproducing manually, or asking users to send screenshots.

A weak React auth system fails silently:

  • login sometimes loops,
  • a user is suddenly logged out,
  • a permission appears for one tab but not another,
  • a refresh storm overloads the API,
  • a tenant switch shows stale data,
  • users report “I cannot open the case,”
  • the backend sees 403, the frontend shows a spinner,
  • support cannot explain whether the denial was correct.

A strong auth system treats every auth transition as an observable state change.

The goal is not “log everything.” The goal is to create enough safe telemetry to preserve the system invariants:

  • every request is associated with an auth context,
  • every denial has a machine-readable reason,
  • every login and logout has a traceable lifecycle,
  • every refresh failure is distinguishable from API downtime,
  • every suspicious auth pattern is visible,
  • every support/debugging path avoids leaking credentials or PII,
  • every dashboard helps engineering answer a specific operational question.

OWASP Logging guidance says application logs should capture enough information for monitoring and analysis, including the classic when, where, who, and what dimensions. The important constraint for auth is that observability must not become a data leak. Never log access tokens, refresh tokens, ID tokens, session IDs, authorization codes, CSRF tokens, password reset tokens, magic links, or raw cookies.

Reference anchors:


1. Observability Is Not Debug Logging

Debug logging answers: “What happened in this code path?”

Observability answers: “What is the system doing, and why?”

For React auth, the meaningful observability questions are:

  1. Authentication health — Are users able to login?
  2. Session continuity — Are sessions stable after login?
  3. Authorization correctness — Are users denied for valid reasons?
  4. Privilege freshness — Are permission changes reflected quickly?
  5. Tenant isolation — Are tenant-scoped requests and caches aligned?
  6. Security posture — Are suspicious patterns increasing?
  7. User impact — Which flows, routes, tenants, browsers, and releases are affected?

A console log inside AuthProvider cannot answer those.

You need structured events, metrics, traces, correlation IDs, sampling rules, redaction, and dashboards.


2. The Auth Observability Model

Think in three layers.

Each layer has a different job.

LayerMain useExample
MetricsTrends and alertsLogin success rate dropped below 98%.
LogsInvestigation and evidenceUser denied case.approve because case.state=closed.
TracesCausal pathRoute loader → BFF → policy engine → downstream API.
Audit eventsCompliance/security historyAdmin granted role case_supervisor to user.

Do not merge all four into one concept.

A metric is aggregated. A log is investigative. A trace is causal. An audit event is evidence.


3. What React Should Emit

The frontend should not emit secrets or authoritative security decisions. It should emit user-observable auth transitions and request context.

Good frontend events:

export type FrontendAuthTelemetryEvent =
  | {
      type: 'auth.session.bootstrap.started';
      routeId: string;
      authEpoch?: string;
    }
  | {
      type: 'auth.session.bootstrap.completed';
      result: 'anonymous' | 'authenticated' | 'expired' | 'degraded';
      durationMs: number;
      routeId: string;
      authEpoch?: string;
      tenantIdHash?: string;
    }
  | {
      type: 'auth.login.redirect.started';
      provider: 'oidc' | 'saml' | 'password' | 'passkey' | 'unknown';
      reason: 'explicit_login' | 'session_required' | 'step_up_required';
      returnToKind: 'internal_path' | 'blocked_external' | 'none';
    }
  | {
      type: 'auth.logout.client.completed';
      reason: 'user_clicked' | 'session_revoked' | 'cross_tab' | 'forced' | 'tenant_switch';
      clearedQueryCache: boolean;
      clearedPermissionCache: boolean;
    }
  | {
      type: 'auth.permission.ui.denied';
      action: string;
      resourceType?: string;
      reasonCode: string;
      routeId?: string;
      decisionSource: 'projection' | 'server_error' | 'unknown';
    }
  | {
      type: 'auth.redirect.loop.prevented';
      routeId: string;
      attemptedCount: number;
    };

Bad frontend events:

telemetry.track('token', { accessToken });
telemetry.track('cookie', { documentCookie: document.cookie });
telemetry.track('user', { email, fullJwt, rawClaims });
telemetry.track('permissionTrace', { fullPolicyInput });

The frontend is the worst place to collect sensitive auth detail. Browser telemetry is exposed to SDKs, extensions, proxies, network tooling, and accidental vendor ingestion.

Use stable identifiers or hashes where possible. Prefer userIdHash, tenantIdHash, sessionIdHash, authEpoch, correlationId, and routeId over raw identity data.


4. Metrics That Matter

A production auth system needs a small set of strong metrics.

4.1 Login Funnel Metrics

Track the login lifecycle as a funnel.

Useful metrics:

MetricTypeMeaning
auth_login_started_totalcounterLogin attempts started.
auth_login_callback_totalcounterCallback reached app/BFF.
auth_login_completed_totalcounterSession established.
auth_login_failed_totalcounterLogin failed by reason.
auth_login_duration_mshistogramEnd-to-end login latency.
auth_login_redirect_loop_totalcounterRedirect loop prevention triggered.

Labels should be controlled:

provider=oidc|saml|password|passkey
client=spa|bff|ssr
result=success|failed|cancelled
default_reason=invalid_state|provider_error|callback_error|session_create_failed|network_error|unknown

Avoid high-cardinality labels like raw URL, user email, organization name, JWT subject, or full error message.

4.2 Session Continuity Metrics

Session bugs are often invisible because users describe them as “it logged me out.”

Track:

MetricMeaning
auth_session_bootstrap_totalNumber of app bootstrap auth checks.
auth_session_bootstrap_duration_ms/session or /me latency.
auth_session_expired_totalExpired sessions observed.
auth_session_revoked_totalRevoked sessions observed.
auth_refresh_started_totalRefresh attempts.
auth_refresh_completed_totalSuccessful refresh.
auth_refresh_failed_totalFailed refresh by reason.
auth_refresh_reuse_detected_totalRefresh token reuse or family invalidation.
auth_cross_tab_logout_totalLogout propagated across tabs.

For refresh, reason labels matter:

reason=expired_refresh_token
reason=network_error
reason=reuse_detected
reason=session_revoked
reason=idp_unavailable
reason=clock_skew
reason=concurrent_refresh_lost

Do not collapse these into refresh_failed only. They imply different actions.

4.3 Authorization Metrics

Authorization metrics answer: “Are denials normal, wrong, suspicious, or caused by a release?”

Track:

MetricMeaning
authz_decision_totalCount of allow/deny decisions.
authz_denied_totalDenials by action/resource/reason.
authz_denied_ui_totalUI-level denial projection.
authz_denied_api_totalServer/API denial.
authz_stale_permission_totalUI thought allow, server denied due to stale permission.
authz_tenant_mismatch_totalTenant context mismatch detected.
authz_step_up_required_totalDenials recoverable through stronger auth.

Useful labels:

action=case.approve|case.assign|document.download
resource_type=case|document|tenant|user
result=allow|deny
reason=missing_permission|wrong_tenant|workflow_state|requires_step_up|separation_of_duties|stale_permission
source=ui_projection|api|bff|policy_engine

High-cardinality warning: action and resource type are usually safe. Resource ID is not.


5. Auth SLOs

A dashboard without target behavior becomes decoration.

Define SLOs for important auth journeys.

Example:

JourneySLOError budget symptom
Login completion99.5% of started logins complete within 30sIdP outage, callback bug, redirect loop.
Session bootstrapp95 under 500msSlow /session, policy service dependency.
Token refresh99.9% success excluding intentional expiryrefresh race, clock skew, IdP/network issue.
Permission projectionp95 under 700mspolicy engine slow, permission endpoint degraded.
Auth API denial correctnesszero known fail-openincident, rollback, forced review.
Logout cleanup99.9% clears local auth cachestale data, bfcache, multi-tab issue.

A mature team distinguishes availability SLOs from security invariants.

A login latency SLO can have an error budget. A fail-open authorization bug cannot.


6. Correlation ID Design

Correlation IDs connect frontend events, backend logs, traces, and support tickets.

You need at least three IDs:

IDScopePurpose
requestIdsingle HTTP requestDebug one request.
traceIddistributed call chainConnect frontend/BFF/API/policy engine.
authCorrelationIdauth journeyConnect login/refresh/logout across redirects.

Example flow:

For W3C trace context, prefer standard headers like traceparent. For user-visible support, expose a safe requestId or supportCode, not the full internal trace payload.

Do not put secrets into correlation headers.


7. Structured Logging Schema

A log line should be a structured event, not a sentence.

Bad:

User can't access case 123 maybe wrong role??? token=eyJhbGci...

Good:

{
  "event": "authz.decision",
  "timestamp": "2026-07-08T10:31:42.120Z",
  "service": "case-bff",
  "environment": "prod",
  "request_id": "req_7k9",
  "trace_id": "0af7651916cd43dd8448eb211c80319c",
  "actor_id_hash": "usrh_2d9f",
  "tenant_id_hash": "tenh_a18c",
  "session_id_hash": "sesh_91d2",
  "action": "case.approve",
  "resource_type": "case",
  "resource_id_hash": "caseh_812a",
  "result": "deny",
  "reason_code": "WORKFLOW_STATE_NOT_APPROVABLE",
  "policy_version": "case-policy@42",
  "auth_epoch": "ae_184",
  "permission_epoch": "pe_991",
  "http_status": 403
}

The event has enough context to investigate, but avoids raw sensitive values.

export type AuthLogBase = {
  event: string;
  timestamp: string;
  environment: 'dev' | 'staging' | 'prod';
  service: string;
  version: string;
  requestId?: string;
  traceId?: string;
  spanId?: string;
  routeId?: string;
  actorIdHash?: string;
  subjectIdHash?: string;
  tenantIdHash?: string;
  sessionIdHash?: string;
  authEpoch?: string;
  permissionEpoch?: string;
};

Auth-specific Fields

export type AuthDecisionLog = AuthLogBase & {
  event: 'authz.decision';
  action: string;
  resourceType: string;
  resourceIdHash?: string;
  result: 'allow' | 'deny';
  reasonCode?: string;
  policyVersion?: string;
  decisionSource: 'bff' | 'api' | 'policy_engine';
  httpStatus?: 200 | 401 | 403 | 404 | 409;
};

Hashing is not magic anonymization. Treat hashed identifiers as sensitive metadata if they can be linked across systems.


8. Redaction Rules

Auth logs are high-risk because the most useful debugging artifacts are often the most dangerous.

Never log:

  • access tokens,
  • refresh tokens,
  • ID tokens,
  • raw JWT payload if it contains PII,
  • authorization code,
  • PKCE verifier,
  • CSRF token,
  • session cookie,
  • password reset token,
  • magic link,
  • WebAuthn challenge response,
  • raw Cookie header,
  • raw Authorization header,
  • full Set-Cookie header,
  • raw SAML assertion,
  • raw OAuth callback URL with code or token-like params.

Implement redaction as infrastructure, not developer memory.

const SECRET_HEADER_NAMES = new Set([
  'authorization',
  'cookie',
  'set-cookie',
  'x-csrf-token',
  'x-api-key',
]);

const SECRET_QUERY_PARAMS = new Set([
  'code',
  'access_token',
  'id_token',
  'refresh_token',
  'state',
  'nonce',
  'token',
]);

export function sanitizeUrlForLogs(input: string): string {
  const url = new URL(input, 'https://placeholder.invalid');

  for (const key of [...url.searchParams.keys()]) {
    if (SECRET_QUERY_PARAMS.has(key.toLowerCase())) {
      url.searchParams.set(key, '[REDACTED]');
    }
  }

  return url.pathname + (url.search ? url.search : '');
}

export function sanitizeHeadersForLogs(headers: Headers): Record<string, string> {
  const output: Record<string, string> = {};

  headers.forEach((value, key) => {
    output[key] = SECRET_HEADER_NAMES.has(key.toLowerCase()) ? '[REDACTED]' : value;
  });

  return output;
}

Test redaction as a security control.


9. Frontend Telemetry Without Leaking Secrets

A React telemetry adapter should be explicit.

type SafeTelemetryContext = {
  routeId?: string;
  requestId?: string;
  traceId?: string;
  release: string;
  authState: 'unknown' | 'anonymous' | 'authenticated' | 'expired' | 'forbidden' | 'degraded';
  authEpoch?: string;
  tenantIdHash?: string;
};

type SafeEventPayload = Record<string, string | number | boolean | null | undefined>;

export interface TelemetryClient {
  track(event: string, payload?: SafeEventPayload): void;
  increment(metric: string, labels?: Record<string, string>): void;
  timing(metric: string, durationMs: number, labels?: Record<string, string>): void;
}

export function trackAuthStateTransition(
  telemetry: TelemetryClient,
  from: SafeTelemetryContext['authState'],
  to: SafeTelemetryContext['authState'],
  ctx: SafeTelemetryContext,
) {
  telemetry.track('auth.state.transition', {
    from,
    to,
    routeId: ctx.routeId,
    release: ctx.release,
    authEpoch: ctx.authEpoch,
    tenantIdHash: ctx.tenantIdHash,
  });
}

Notice the absence of raw user identity.

Frontend telemetry should be useful enough to detect UI loops and client-side failures, but not so detailed that it becomes a shadow auth database.


10. Observing Login Redirect Loops

Redirect loops are among the most common production auth bugs.

Symptoms:

  • page flashes login/app/login/app,
  • URL changes repeatedly,
  • users cannot return after SSO,
  • callback route re-enters login,
  • middleware protects callback route by mistake,
  • session bootstrap returns unknown forever.

Track the loop directly.

const LOGIN_ATTEMPT_KEY = 'app.auth.loginAttemptWindow';

export function detectRedirectLoop(now = Date.now()) {
  const raw = sessionStorage.getItem(LOGIN_ATTEMPT_KEY);
  const window = raw ? JSON.parse(raw) as { startedAt: number; count: number } : null;

  const next = !window || now - window.startedAt > 30_000
    ? { startedAt: now, count: 1 }
    : { startedAt: window.startedAt, count: window.count + 1 };

  sessionStorage.setItem(LOGIN_ATTEMPT_KEY, JSON.stringify(next));

  if (next.count >= 3) {
    return { loop: true, count: next.count };
  }

  return { loop: false, count: next.count };
}

When loop prevention triggers, emit:

{
  "event": "auth.redirect.loop.prevented",
  "count": 3,
  "route_id": "case.detail",
  "has_session_projection": false,
  "callback_route_recently_seen": true,
  "release": "2026.07.08-1"
}

Then show a recovery UI:

  • clear local transient auth transaction,
  • provide retry login,
  • provide support code,
  • avoid infinite automatic redirect.

11. Observing Refresh Storms

Refresh storms happen when many requests independently decide the token/session is expired and all trigger refresh.

Frontend symptoms:

  • dozens of /refresh or /session/renew calls,
  • browser freezes,
  • API sees spike in auth traffic,
  • users get logged out due to reuse detection,
  • backend rate limit triggers.

Track these metrics:

auth_refresh_started_total{source="api_client"}
auth_refresh_joined_existing_total
auth_refresh_concurrent_suppressed_total
auth_refresh_failed_total{reason="reuse_detected"}
auth_request_replayed_after_refresh_total
auth_request_replay_failed_total

Single-flight refresh should emit both start and join events.

class RefreshCoordinator {
  private inFlight: Promise<void> | null = null;

  constructor(private telemetry: TelemetryClient) {}

  async refresh(refreshFn: () => Promise<void>) {
    if (this.inFlight) {
      this.telemetry.increment('auth_refresh_joined_existing_total');
      return this.inFlight;
    }

    this.telemetry.increment('auth_refresh_started_total');

    this.inFlight = refreshFn()
      .then(() => this.telemetry.increment('auth_refresh_completed_total'))
      .catch((error) => {
        this.telemetry.increment('auth_refresh_failed_total', {
          reason: classifyRefreshError(error),
        });
        throw error;
      })
      .finally(() => {
        this.inFlight = null;
      });

    return this.inFlight;
  }
}

If joined_existing_total is near zero but refresh calls spike, your API client probably bypasses the coordinator.


12. Observing 401 vs 403 Correctly

A mature auth dashboard never merges 401 and 403.

StatusMeaningLikely user recovery
401Not authenticated / invalid credentialRe-authenticate.
403Authenticated but not allowedRequest access, change tenant, step-up, or stop.
404Could be not found or concealed forbiddenUsually no detail.
409Workflow/resource state conflictRefresh resource state.
429Rate limitedWait/retry later.

Track them separately:

http_client_error_total{status="401", route_id="case.detail"}
http_client_error_total{status="403", action="case.approve"}
authz_denied_total{reason="workflow_state"}
auth_session_expired_total{source="api_response"}

If 403 spikes after a deploy, possible causes:

  • permission vocabulary changed,
  • route metadata drifted,
  • backend policy deployed before frontend projection,
  • tenant context missing,
  • new workflow state not recognized,
  • role sync from IdP failed.

If 401 spikes:

  • session cookie not sent,
  • CORS credentials broken,
  • SameSite cookie behavior changed,
  • refresh failing,
  • clock skew,
  • IdP outage,
  • token audience mismatch,
  • service worker served stale app shell.

13. Dashboards

Create dashboards by question, not by tool.

13.1 Auth Executive Health

Purpose: quickly know if auth is healthy.

Panels:

  • login success rate,
  • login p95 duration,
  • session bootstrap p95,
  • refresh success rate,
  • 401 rate,
  • 403 rate,
  • redirect loops,
  • tenant mismatch,
  • top auth errors by release,
  • affected tenants/users count.

13.2 Login and SSO Dashboard

Panels:

  • started vs callback vs completed,
  • provider errors,
  • invalid state count,
  • invalid nonce count,
  • callback latency,
  • SAML/OIDC org discovery failures,
  • IdP-specific failure distribution.

13.3 Session Continuity Dashboard

Panels:

  • bootstrap result distribution,
  • refresh attempts/success/failure,
  • refresh race indicators,
  • forced logout count,
  • session revoked count,
  • cross-tab logout propagation,
  • bfcache stale restore detections,
  • cache cleanup failures.

13.4 Authorization Dashboard

Panels:

  • decision allow/deny ratio,
  • top denied actions,
  • top denial reasons,
  • stale permission denials,
  • tenant mismatch,
  • step-up required,
  • UI allow/server deny mismatch,
  • policy version distribution.

13.5 Security Signals Dashboard

Panels:

  • invalid callback transaction,
  • open redirect blocked,
  • CSRF validation failed,
  • suspicious refresh reuse,
  • account lockout/rate limit,
  • token replay/reuse signals,
  • impossible tenant switch,
  • repeated forbidden attempts.

14. Alerting Rules

Alert on symptoms that require action.

Bad alert:

Alert when any auth error occurs.

This creates noise.

Better:

Alert when login completion rate < 98% for 10 minutes in prod.
Alert when refresh failure rate > 2% for 5 minutes.
Alert when redirect loops exceed baseline by 5x after a release.
Alert when invalid OAuth state spikes above baseline.
Alert when tenant mismatch is non-zero after deployment.
Alert when UI allow/server deny mismatch exceeds threshold.
Alert when refresh token reuse detection occurs.

Some security alerts should be low-volume but urgent:

  • refresh token reuse detected,
  • CSRF validation failures spike,
  • open redirect blocked spike,
  • admin impersonation without matching support ticket,
  • permission grant outside approval workflow,
  • break-glass access used,
  • many users denied for a normally common action after deploy.

15. Release-aware Auth Observability

Every auth event should include release/version.

Why?

Auth bugs often appear after deployment:

  • frontend route metadata changed,
  • backend policy schema changed,
  • cookie domain changed,
  • CSP changed,
  • SDK upgraded,
  • service worker cache served mixed versions,
  • policy engine changed denial reason codes.

Use release labels carefully. Version is low cardinality if controlled.

{
  "event": "auth.session.bootstrap.completed",
  "release": "web@2026.07.08.3",
  "api_version": "case-api@8f12a",
  "policy_version": "authz@42",
  "result": "authenticated",
  "duration_ms": 184
}

Dashboards should allow filtering by release.


16. Observability for Tenant Context

Multi-tenant auth fails in subtle ways.

Track:

  • active tenant in session projection,
  • tenant in route/resource request,
  • tenant in permission projection,
  • tenant in query cache key,
  • tenant in server decision,
  • tenant switch event,
  • tenant mismatch denial.

Do not log raw tenant name if it is customer-identifying. Use stable hash or internal non-sensitive tenant ID if allowed by your data policy.

Example tenant mismatch event:

{
  "event": "auth.tenant.mismatch",
  "actor_id_hash": "usrh_234",
  "session_tenant_hash": "tenh_a",
  "route_tenant_hash": "tenh_b",
  "resource_tenant_hash": "tenh_b",
  "action": "case.view",
  "result": "deny",
  "reason_code": "TENANT_CONTEXT_MISMATCH",
  "request_id": "req_998"
}

This is one of the highest-value auth observability events in enterprise apps.


17. Observability for Permission Drift

Permission drift means the frontend projection and backend enforcement disagree.

Examples:

  • UI shows Approve, API returns 403.
  • UI hides Assign, API would allow it.
  • Sidebar shows route, loader redirects forbidden.
  • Query cache has permissions from previous tenant.
  • Token claims say role admin, policy engine says role revoked.

Track drift explicitly:

export function detectPermissionDrift(input: {
  uiDecision: 'allow' | 'deny' | 'unknown';
  serverStatus: number;
  action: string;
  resourceType: string;
  permissionEpoch?: string;
}) {
  if (input.uiDecision === 'allow' && input.serverStatus === 403) {
    return {
      type: 'authz.permission_drift.ui_allowed_server_denied',
      action: input.action,
      resourceType: input.resourceType,
      permissionEpoch: input.permissionEpoch,
    };
  }

  return null;
}

Do not auto-suppress drift as “expected.” Drift is a signal that your projection contract, cache invalidation, or release sequencing needs review.


18. Debugging Without Leaking

Support and engineering need enough detail to debug denied access. They do not need raw tokens.

Provide a safe debug bundle:

export type SafeAuthDebugSnapshot = {
  supportCode: string;
  routeId: string;
  authState: string;
  tenantIdHash?: string;
  authEpoch?: string;
  permissionEpoch?: string;
  release: string;
  lastRequestId?: string;
  lastAuthErrorCode?: string;
  lastAuthzReasonCode?: string;
  browserTimeIso: string;
};

User-facing copy:

Access denied. Support code: SUP-8K2LQ. Your session is active, but this action is not currently allowed for this case.

Internal logs can map SUP-8K2LQ to request IDs and policy versions.


19. Privacy Boundaries

Auth observability can accidentally become surveillance.

Minimize:

  • raw identity attributes,
  • exact page titles containing case/person names,
  • full URLs with search params,
  • typed form values,
  • uploaded file names,
  • policy input with resource metadata,
  • denial reasons that expose hidden resource existence.

Use a data classification table.

DataTelemetry handling
Access tokennever collect
Refresh tokennever collect
Session IDhash only if necessary
User IDhash or internal ID depending policy
Emailavoid; redact by default
Tenant ID/namehash or internal non-sensitive ID
Resource IDhash; often avoid in metrics
Action nameallowed, controlled vocabulary
Reason codeallowed, controlled vocabulary
Full URLsanitize path/query
Stack tracecollect server-side with redaction

20. Production Checklist

Use this checklist before calling auth observability production-ready.

## Auth Observability Checklist

### Metrics
- [ ] Login funnel metrics exist.
- [ ] Session bootstrap metrics exist.
- [ ] Refresh success/failure metrics exist.
- [ ] 401 and 403 are separated.
- [ ] Authorization denials are labeled by action/resource type/reason.
- [ ] Tenant mismatch has a dedicated signal.
- [ ] Permission drift has a dedicated signal.
- [ ] Redirect loop prevention has a dedicated signal.

### Logs
- [ ] Logs are structured.
- [ ] Request ID and trace ID are present.
- [ ] Actor/session/tenant identifiers are safe.
- [ ] Tokens/cookies/secrets are redacted.
- [ ] Auth errors use machine-readable codes.
- [ ] Policy version is logged for authorization decisions.

### Traces
- [ ] Frontend request ID connects to BFF/API logs.
- [ ] BFF/API traces connect to policy engine calls.
- [ ] Downstream API calls preserve trace context.
- [ ] User-facing support code maps to internal request/trace IDs.

### Dashboards
- [ ] Login health dashboard exists.
- [ ] Session continuity dashboard exists.
- [ ] Authorization denial dashboard exists.
- [ ] Security signals dashboard exists.
- [ ] Dashboards support release filtering.

### Alerts
- [ ] Login completion drop alert exists.
- [ ] Refresh failure spike alert exists.
- [ ] Redirect loop spike alert exists.
- [ ] Tenant mismatch alert exists.
- [ ] Refresh reuse/security alert exists.
- [ ] Permission drift alert exists.

### Privacy
- [ ] No tokens are logged.
- [ ] No raw cookies are logged.
- [ ] URLs are sanitized.
- [ ] PII collection is minimized.
- [ ] Telemetry vendor configuration is reviewed.

21. Mental Model

Auth observability is the control plane for auth correctness in production.

Without it, you only know what users complain about.

With it, you can answer:

  • Is login broken or only one provider?
  • Is refresh failing or is the API unavailable?
  • Is this 403 correct or a policy regression?
  • Is the frontend permission cache stale?
  • Did tenant switch clear all scoped caches?
  • Did logout actually propagate across tabs?
  • Did the new release increase redirect loops?
  • Are we leaking sensitive data into telemetry?

A top-level React engineer does not merely build auth UI. They build an auth system that can be operated, debugged, audited, and trusted under failure.

Lesson Recap

You just completed lesson 101 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.