API Authentication: When to Use JWT, Session Cookies, and API Keys
Every PHP application that communicates with an API needs to prove to that API who is making the request. This is called authentication, and getting it wrong is one of the most common sources of security vulnerabilities and integration failures. The three most common approaches are JWT (JSON Web Tokens), session cookies, and static API keys. Each works well in different situations, and choosing the wrong one for your scenario creates unnecessary complexity or outright security problems.
This guide explains what each approach actually does, where it works best, and how to implement each one securely in PHP. By the end you will know which method fits your use case and how to avoid the mistakes that cause the most common authentication failures in PHP applications. If you are building a business web application and want to understand how these authentication methods fit into a broader API strategy, a review of API design principles for business applications may be useful alongside this guide.
How API Key Authentication Works
An API key is a static string that identifies the client making the request. The server checks the key against a list of valid keys and grants access if the key is recognised. This is the simplest of the three approaches and has been used for API authentication since the earliest public APIs became available.
API keys are typically passed in a request header:
GET /api/resource HTTP/1.1
Host: api.example.com
X-API-Key: sk_live_a1b2c3d4e5f6g7h8i9j0
The server reads the header, looks up the key in a database or configuration file, and decides whether to process the request. If the key is revoked, the server simply stops accepting requests with that key.
API keys work well in these situations:
- Server-to-server communication: when two backend systems talk to each other and there is no browser involved.
- Public APIs with rate limiting: when you need to identify which application is making requests without tracking individual users.
- Simple integrations: when the overhead of JWT or OAuth is not justified by the sensitivity of the data.
API keys are a poor choice when you need to identify individual users, when you need short-lived access without a backend session store, or when the key would be exposed in a browser where any user can read it. Storing API keys in client-side JavaScript or mobile app code that can be decompiled creates a significant security risk.
How Session Cookie Authentication Works
Session authentication uses a server-side session to track logged-in users. When a user submits credentials, the server validates them and stores a session identifier in a database or memory store. The server sends this identifier back to the client as a cookie. On every subsequent request, the browser automatically sends the cookie, and the server looks up the session to identify the user.
In PHP, the session mechanism is built into the language:
session_start();
if (password_verify($_POST['password'], $stored_hash)) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['login_time'] = time();
}
PHP handles the cookie transmission automatically. The developer only needs to check $_SESSION on each request to know who is making the request. Sessions integrate well with PHP's CSRF protection mechanisms, which are important for any application that uses forms.
Sessions work well in these situations:
- Traditional web applications: when the application renders HTML pages on the server and serves them to a browser.
- Applications that need server-side state: when you need to store user data that should not be exposed to the client.
- When CSRF protection is straightforward: session-based authentication integrates naturally with PHP's built-in token mechanisms.
Sessions do not work well for pure API-driven applications where the frontend is decoupled from the backend, or when the API needs to be accessible from mobile apps or external services that do not maintain browser cookies. For a deeper look at the security considerations of PHP sessions, including common vulnerabilities and fixes, the PHP session security guide covers the specific problems that are easy to miss until they cause issues.
How JWT Authentication Works
A JSON Web Token is a self-contained string that carries claims about the user. The token is signed by the server using a secret key or private key, and the recipient can verify the signature without consulting a database. This makes JWTs particularly useful for stateless authentication across multiple services.
A JWT has three parts separated by dots: the header, the payload, and the signature. The payload contains the claims, such as the user ID and expiration time. Here is a minimal example of decoding a JWT in PHP using a popular library:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token = str_replace('Bearer ', '', $token);
$decoded = JWT::decode($token, new Key($secret, 'HS256'));
$user_id = $decoded->sub;
When a user logs in, the server creates a token with an expiration time, signs it, and sends it to the client. The client stores the token and sends it with every request. The server verifies the signature on each request without querying a database, which removes a database round-trip on every authenticated request.
JWTs work well in these situations:
- Microservices and distributed systems: when multiple services need to verify user identity without querying a central database.
- Single Page Applications: when the frontend and backend are separate applications and cookies are inconvenient.
- Short-lived access: when you need to grant temporary access that expires automatically.
JWTs have real limitations that cause problems when ignored. The server cannot revoke a token before it expires because verification is stateless. If a token is compromised, the attacker has access until the expiration time. Storing sensitive data in the payload is unsafe because the payload is readable by anyone who Base64-decodes the token. These limitations mean JWTs are not a drop-in replacement for sessions in every situation. Applications that need immediate revocation should implement a token blocklist or choose a shorter expiration window.
Comparing the Three Approaches
Each method makes different trade-offs. Understanding these trade-offs helps you choose the right method for each part of your application rather than forcing a single pattern everywhere.
The key differences between these authentication methods:
- State: API keys are stateless and static. Sessions are stateful and server-side. JWTs are stateless but self-contained.
- Revocation: API keys and sessions can be revoked immediately. JWTs cannot be revoked without a blocklist or shorter expiration times.
- Storage: API keys need a database lookup. Sessions need a session store. JWTs need only the signing secret.
- Browser support: Sessions work automatically with browsers. API keys should not be stored in browser code. JWTs can be stored in browser localStorage or httpOnly cookies.
- Scalability: JWTs scale horizontally without shared state. Sessions require a shared session store across all servers. API key lookups scale with database performance.
For PHP applications specifically, the choice often comes down to whether the application is server-rendered or API-driven. Server-rendered applications naturally benefit from session cookies. API-first architectures typically work better with JWTs or API keys. Many real-world applications use a combination of these methods, which is perfectly reasonable as long as each is applied in the appropriate context.
Common Mistakes and How to Avoid Them
Understanding common authentication mistakes helps you avoid them in your own implementation. These errors appear regularly in both new projects and production systems that have been running for years.
Storing API keys in public repositories is the most common and most damaging mistake with API key authentication. Use environment variables or a secrets manager. Never commit keys to version control, even temporarily. Even private repositories can be exposed through accidents, and any key that has been in version control should be treated as compromised and rotated immediately.
Using sessions for API-only applications creates unnecessary server-side state and complicates scaling. If your application exposes an API that mobile apps, SPAs, or external services consume, prefer JWTs or API keys over sessions. Sessions work best when the user interacts with the application through a browser and the server is rendering HTML pages.
Failing to set secure cookie flags on session cookies exposes sessions to theft via cross-site scripting. Always set the httpOnly and secure flags when configuring session cookies:
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
session_start();
Using the same JWT signing secret across environments is a serious risk. If the secret leaks in a development environment, attackers cannot use it directly in production, but it increases the chance of reuse or accidental exposure. Keep secrets separate per environment and rotate them regularly. Each environment should have its own secret stored securely in environment variables or a secrets manager.
Not validating JWT expiration is another common error. Always check the expiration claim and handle the case where the token has expired. Catching the JWT expiration exception separately lets you distinguish between an expired token and an invalid signature:
try {
$decoded = JWT::decode($token, new Key($secret, 'HS256'));
} catch (ExpiredException $e) {
// Token expired — prompt re-authentication
http_response_code(401);
exit('Token expired');
} catch (\Throwable $e) {
// Invalid token — do not trust it
http_response_code(401);
exit('Invalid token');
}
When to Use Which Method
Use API keys for machine-to-server communication where the client is a server, a CLI tool, or a trusted application. This is the simplest model and it works well for background jobs, webhooks, and internal service calls. API keys are particularly appropriate when the calling service is a backend system you control and the risk of key exposure is manageable through proper secrets management.
Use session cookies for traditional server-rendered web applications where the user interacts with the application through a browser. PHP's built-in session handling is well-tested and secure when configured correctly. For PHP-based applications, sessions remain the most straightforward choice for browser-based user authentication.
Use JWTs when the application needs to be stateless and scale horizontally without shared session storage, or when multiple independent services need to verify the same authentication token. JWTs are also the right choice when the frontend is a separate application from the backend and managing cookies is impractical. For applications that handle sensitive user data, a PHP security checklist for business websites provides a practical overview of the security measures that complement a solid authentication foundation.
In practice, many applications use more than one method. A web application might use sessions for browser-based login and JWTs for API access from mobile clients. A microservices architecture might use API keys for service-to-service calls and JWTs for user-facing endpoints. Choosing the right tool for each interaction rather than forcing one method onto every situation produces simpler and more secure systems.
Protecting Against Authentication Attacks
Regardless of the method chosen, certain protections apply to all authentication systems. Rate limiting on login endpoints prevents brute force attacks. Account lockout or progressive delays after failed attempts make credential stuffing impractical. Multi-factor authentication adds a second verification step for sensitive operations.
Logging authentication events helps detect attacks in progress. Record the IP address, timestamp, and result of every login attempt, including API key presentations. Unusual patterns such as many failed attempts from a single IP or a single account should trigger alerts. This logging is essential for incident response and for understanding normal versus abnormal access patterns in your application.
For API key authentication, storing keys as plain text in the database is a catastrophic risk if the database is compromised. Always store a hashed version of the key and verify by hashing the presented key and comparing the results. This means you cannot show the user the full key after initial creation, but it prevents a database leak from exposing valid API keys. Treat your API keys with the same care you would treat password hashes.
The OWASP Top 10 for business web applications provides a broader view of the security risks that authentication systems should protect against. Broken authentication appears consistently in these lists because it remains one of the most exploited attack surfaces in web applications.
Summary
API key authentication is the simplest option for server-to-server communication where the calling service is a trusted backend system. Session cookies are the right choice for traditional browser-based web applications built with PHP, where the server renders HTML pages and the user interacts through a browser. JWTs provide stateless authentication for distributed systems and decoupled frontends, but they require careful handling of expiration and revocation logic.
Choosing the wrong method for the situation adds unnecessary complexity and can introduce security vulnerabilities. Understanding what each method actually does, rather than applying a single pattern everywhere, is what produces secure and maintainable authentication systems. Many PHP applications benefit from using different authentication methods for different parts of the system, and that mixed approach is not a sign of poor architecture.
If you are building or maintaining a PHP application and want a practical security review of your current authentication setup, you can get in touch with details of what you have implemented and what concerns you want to address.