×
Premium WordPress plugins, PHP Scripts, Android ios games, and Apps. Download Nulled PHP Scripts, Codecanyon Scripts, App Source Code, WordPress Themes here And Many More.
API Authentication in PHP: JWT, OAuth, and Choosing Between Them

Why Session Cookies Are Not the Answer for an API

Traditional cookie-based sessions work well for a single web application but assume a stateful server tracking session data and a browser automatically sending cookies back. An API consumed by a mobile app, a third-party integration, or a separate frontend served from a different domain needs an authentication approach that does not depend on either of those assumptions. JWT (JSON Web Tokens) and OAuth are the two patterns that dominate modern API authentication, and they solve related but distinct problems.

JWT: A Self-Contained, Verifiable Token

A JWT is a signed (and optionally encrypted) token containing claims — data about the user and the token itself — that any server holding the right key can verify without a database lookup, since the token's signature proves it has not been tampered with since issuance.

$payload = ['sub' => $user->id, 'exp' => time() + 3600, 'iat' => time()];
$token = \Firebase\JWT\JWT::encode($payload, config('app.jwt_secret'), 'HS256');

// verifying:
$decoded = \Firebase\JWT\JWT::decode($token, new Key(config('app.jwt_secret'), 'HS256'));

This statelessness is JWT's main appeal — no shared session store needed across multiple API servers — and its main operational headache: a JWT cannot be "logged out" or revoked early in the simple case, since the server has no record of issued tokens to invalidate. It remains valid until it naturally expires, which is why short expiry times paired with a refresh-token mechanism are the standard mitigation.

Refresh Tokens: Balancing Short-Lived Access With Usability

Issuing a short-lived access token (minutes to an hour) alongside a longer-lived, revocable refresh token solves the "can't revoke a JWT" problem practically: a compromised access token is only dangerous for a short window, while the refresh token, stored server-side and checked against the database on each use, can be revoked immediately if needed.

$accessToken = $this->issueJwt($user, ttl: 900); // 15 minutes
$refreshToken = $this->issueRefreshToken($user); // stored in DB, revocable, longer-lived

// later, client uses refresh token to get a new access token:
public function refresh(Request $request) {
    $stored = RefreshToken::where('token', hash('sha256', $request->refresh_token))->where('revoked', false)->first();
    abort_if(!$stored || $stored->isExpired(), 401);
    return response()->json(['access_token' => $this->issueJwt($stored->user)]);
}

OAuth 2.0: A Different Problem — Delegated Authorization

OAuth solves a related but genuinely different problem than plain authentication: letting a user grant a third-party application limited access to their data on another service, without ever sharing their actual password with that third party. "Sign in with Google" is OAuth in action — your application never sees the user's Google password, only a token Google issues after the user explicitly approves the specific access your application requested.

The Authorization Code Flow

The most common and most secure OAuth flow for a server-side application: the user is redirected to the provider (Google, GitHub) to approve access, the provider redirects back with a short-lived authorization code, and your server exchanges that code (along with a secret only your server knows) for an access token — the actual token never passes through the user's browser at all, which protects it from interception.

// Step 1: redirect user to provider
return redirect($provider->getAuthorizationUrl());

// Step 2: provider redirects back with a code; exchange it server-side
$token = $provider->getAccessToken('authorization_code', [
    'code' => $request->get('code'),
]);

When to Use JWT Versus OAuth

These are not competing choices for the same problem. Use JWT (with refresh tokens) for authenticating your own users against your own API — a mobile app talking to your backend, a single-page application calling your API. Use OAuth when a third party needs delegated, scoped access to a user's data, or when you want to let users sign in using an identity they already have elsewhere (Google, GitHub) rather than managing a separate password for your application. Many real systems use both: OAuth for the initial sign-in, followed by your own JWT-based session for subsequent API calls.

Common API Authentication Mistakes

Storing JWTs in browser localStorage, which is readable by any JavaScript running on the page, making a successful XSS attack capable of stealing the token directly — an HttpOnly cookie is generally a safer storage location for web clients. Using an overly long-lived access token to avoid implementing refresh logic, which increases the damage window if that token is ever leaked. Not validating the exp (expiry) claim correctly, or trusting a token's claims without verifying its signature first, which defeats the entire point of using a signed token in the first place. Reusing the same signing secret across environments (development and production sharing one secret), so a leak in a less-guarded environment compromises production tokens too.

Closing Thought

JWT and OAuth solve different problems that are easy to conflate because both involve tokens and both commonly show up in the same systems. Understanding that one is about proving who a user is to your own API, and the other is about granting scoped, delegated access to a third party, clarifies which one (often both, for different parts of a system) a given API actually needs — rather than reaching for OAuth's redirect-based complexity for a problem plain JWT authentication would solve more simply, or vice versa.

Building an API that needs proper authentication? We can design the right approach for your use case.

Scopes: Limiting What an OAuth Token Can Actually Do

OAuth tokens are not all-or-nothing — scopes let the requesting application ask for, and the user explicitly approve, a specific limited set of permissions ("read your profile" but not "post on your behalf"), rather than full account access. Designing scopes thoughtfully for your own API (if you act as an OAuth provider for third-party integrations) is what lets users trust connecting third-party apps without handing over broad, unnecessary access:

$grant->setScopes(['profile:read', 'orders:read']);
// the third-party app receives a token valid only for these specific scopes

Token Revocation: Letting Users (and You) Cut Off Access

A complete API authentication system needs a clear way to revoke access — a user disconnecting a third-party app they no longer trust, or your own team revoking a compromised API key. For refresh tokens and OAuth tokens, this means an explicit revocation endpoint and a database check on every use; for short-lived JWTs without server-side state, revocation effectively means waiting out the (short) remaining expiry, which is precisely why keeping JWT lifetimes short matters so much in practice, not just in theory.

public function revoke(Request $request) {
    RefreshToken::where('token', hash('sha256', $request->token))->update(['revoked' => true]);
}

API Keys: A Simpler Alternative for Server-to-Server Communication

Not every API consumer is a human user authenticating interactively — a server-to-server integration often makes more sense authenticated with a long-lived API key than with a JWT/OAuth flow designed around interactive user login. API keys should still be treated carefully: hashed at rest (never stored in plaintext, similar to passwords), scoped to specific permissions rather than full account access, and easily rotatable without requiring a full re-integration on the consumer's side.

Rate Limiting API Authentication Endpoints

Login and token-refresh endpoints are prime targets for credential-stuffing and brute-force attacks precisely because they are the entry point to everything else an attacker might want access to. Rate limiting these specific endpoints more aggressively than general API traffic, keyed by both IP address and the account being targeted, is a baseline defense that a surprising number of APIs skip, treating authentication endpoints the same as any other route despite their meaningfully higher risk profile.

Route::post('/login', [AuthController::class, 'login'])
    ->middleware('throttle:5,1'); // 5 attempts per minute per IP

Logging and Monitoring Authentication Events

Failed login attempts, unusual token refresh patterns, and access from unfamiliar locations are all signals worth capturing and, for sensitive applications, alerting on. A sudden spike in failed logins against a specific account, or a valid token suddenly being used from a country the account has never logged in from before, are exactly the kind of anomalies that good logging surfaces and that silent, unmonitored authentication code misses entirely until real damage has already occurred.

Mobile App Considerations: Token Storage on Device

A mobile app cannot rely on HttpOnly cookies the way a web browser can, so tokens need a different secure storage strategy — the platform-provided secure storage (Keychain on iOS, Keystore-backed encrypted storage on Android) rather than plain shared preferences or unencrypted local files, which are far more easily extracted from a compromised or rooted/jailbroken device.

Single Sign-On Across Multiple Related Applications

For organizations running several related applications that should share one login, a centralized identity provider issuing tokens that every application trusts (rather than each application maintaining its own separate user database and login flow) avoids both a fragmented user experience and the security risk of maintaining multiple, possibly inconsistently-secured authentication implementations across a growing suite of applications.

Case Study: A Token That Outlived the Trust That Issued It

A fintech startup issued JWTs with a 30-day expiry to reduce how often users needed to log back in, reasoning that frequent re-authentication hurt user experience. When a former employee's laptop, still holding a valid 30-day token from before their access was supposed to be revoked, was used to access the company's own internal API weeks after their departure, the security team discovered the access-revocation process only removed the user's database record — it had no mechanism to invalidate already-issued long-lived JWTs, which remained valid as bearer tokens regardless of what happened to the underlying account afterward. The fix combined two changes: shortening access token lifetime dramatically (down to 15 minutes) paired with revocable refresh tokens as described earlier in this guide, and adding an explicit check during JWT verification against a short list of recently-revoked user IDs for the rare cases where immediate revocation genuinely mattered. The broader lesson: convenience tradeoffs made for good reasons (fewer logins) can quietly create security gaps that only become visible during an incident, which is exactly why short-lived tokens paired with a proper refresh mechanism are the standard recommendation rather than simply extending JWT lifetime to avoid implementing refresh logic.

A Glossary for This Topic

Bearer token: a token that grants access to whoever presents it, with no further proof of identity required — which is why protecting the token itself in transit and storage matters so much. Claims: the data encoded within a JWT, such as the subject (user ID), expiry, and any custom data the issuer chooses to include. Authorization Code Flow: the standard, most secure OAuth 2.0 flow for server-side applications, exchanging a short-lived code for an access token through a secure server-to-server request. Scope: a specific, limited permission granted to an OAuth token, restricting what the token holder can actually do with it.

Frequently Asked Questions

Should I roll my own JWT library or use an established one? Use an established, well-audited library — token signing and verification have enough subtle correctness and security requirements that a custom implementation is a common source of vulnerabilities that established libraries have already had years to find and fix.

Is OAuth overkill for a simple mobile app talking only to my own API? Generally yes — plain JWT with refresh tokens is simpler and entirely sufficient when there is no third party involved and no delegated access being granted; OAuth's added complexity earns its place specifically when a separate party needs limited, user-approved access to data.

What is the safest place to store a JWT on a web client? An HttpOnly, Secure cookie is generally safer than localStorage, since HttpOnly cookies are inaccessible to JavaScript and therefore immune to token theft via a successful XSS attack, which localStorage-based storage is not protected against at all.

Step-by-Step: Building a JWT-Based API Authentication System

Step one: choose an established JWT library rather than implementing token signing and verification yourself, for the security reasons already covered. Step two: design short-lived access tokens (15 minutes to an hour) carrying only the minimal claims needed for authorization decisions, avoiding sensitive data in the payload since JWT claims are base64-encoded, not encrypted, and readable by anyone holding the token. Step three: implement a database-backed refresh token system, storing only a hash of the refresh token (similar to password storage practices) so a leaked database does not directly expose usable tokens. Step four: build explicit revocation for refresh tokens, exposed through both a user-facing "log out of all devices" feature and an admin-facing emergency revocation capability. Step five: add rate limiting specifically on authentication and token-refresh endpoints, tuned more aggressively than general API rate limits given their higher risk profile. Step six: log authentication events with enough detail to support after-the-fact investigation of suspicious activity, and where appropriate, alert on clear anomalies like a sudden spike in failed attempts against one account.

A Comparison Table: Authentication Mechanisms at a Glance

Session cookies: simplest for traditional server-rendered web applications, requires server-side session storage or sticky sessions across multiple servers, not well suited to mobile or cross-domain API consumers. JWT with refresh tokens: stateless access tokens scale well across multiple API servers, requires careful short-lifetime and refresh-token design to handle revocation properly. OAuth 2.0: the right tool specifically for delegated, third-party access scenarios, not a general-purpose replacement for your own first-party authentication needs. API keys: simplest option for server-to-server integrations with no interactive user login involved, requires careful hashing, scoping, and rotation practices to remain secure long-term.

Security Considerations Checklist

Always verify a JWT's signature before trusting any of its claims, never just decoding and reading the payload without verification. Use short-lived access tokens paired with revocable refresh tokens rather than long-lived tokens for convenience. Store refresh tokens hashed in the database, never in plaintext. Validate the aud (audience) and iss (issuer) claims where relevant, not just expiry, to prevent a token issued for one purpose or service being misused against another. Rotate signing secrets periodically and support graceful key rotation (accepting tokens signed with a recently-retired key for a transition window) rather than invalidating every outstanding token the instant a key rotates.

Accessibility Considerations

Login and authentication flows need careful accessibility attention since they are a mandatory gateway every user must pass through — clear error messages for failed login attempts, properly labeled username/password fields, and keyboard-navigable multi-factor authentication prompts. OAuth consent screens (the "this app wants to access your data" approval page) should clearly and plainly describe what access is being granted in language a non-technical user can actually understand, rather than vague or overly technical scope descriptions that leave users unable to make an informed decision about what they are approving.

How This Plays Out at Different Scales

A small internal API serving a handful of trusted clients can reasonably use simple API keys without the full JWT/refresh-token machinery. A consumer-facing mobile app at meaningful scale needs the complete JWT plus refresh token architecture described in this guide, since user volume and exposure to public networks both increase the value of short-lived tokens and proper revocation. A platform offering its API to third-party developers needs full OAuth support with proper scopes and a developer-facing consent flow, since at that point delegated, user-approved access for external applications becomes a core product requirement rather than an internal implementation detail.

What to Do When You Inherit an API With No Token Expiry at All

Inheriting an API that issues JWTs with no expiry, or an expiry set so far in the future it is effectively permanent, is a serious but fixable security gap rather than a reason to panic. The fix is not simply shortening expiry on the next token version and calling it done — every already-issued long-lived token remains valid until it naturally expires regardless of changes made going forward, so a deliberate rotation plan (forcing re-authentication across all clients within a defined window, paired with monitoring for any unexpected continued use of pre-rotation tokens past that window) is needed to actually close the gap rather than just stop it from growing further.

Final Checklist Before Shipping an Authentication System

Access tokens are short-lived and refresh tokens are properly revocable and hashed at rest. Webhook-equivalent events (login, logout, token refresh, revocation) are logged with enough detail to investigate suspicious activity after the fact. Rate limiting is in place specifically on authentication endpoints, not just general API traffic. OAuth scopes (if used) are specific and clearly described to users at consent time. Signing secrets are environment-specific, never shared between development and production. A documented, tested process exists for emergency revocation of a compromised token or account.

Closing Thought, Revisited

Authentication is the one part of an API where a small oversight compounds silently for as long as it goes unnoticed — a token that should have expired in fifteen minutes but does not get checked for months, an account that should have lost access on departure but retained a still-valid credential for weeks. The checklist and practices in this guide exist specifically to close those silent compounding gaps before they become the kind of incident that is only discovered well after real damage has already occurred.

Handling Clock Skew Between Servers

JWT expiry checks compare a token's exp claim against the verifying server's own clock, so if servers in a distributed system have even slightly unsynchronized clocks, a token can be rejected as expired moments before it should have been, or accepted moments after it should have expired. Most JWT libraries support a small leeway window (a few seconds) specifically to absorb this kind of minor clock drift, and enabling it is a small, easy safeguard against an otherwise confusing class of intermittent, hard-to-reproduce authentication failures that only show up under specific timing conditions across a multi-server deployment.