Introduction: Why OAuth 2.0 and JWT in PHP
Modern web applications rarely work in isolation. Users expect single sign-on via Google, Facebook, or GitHub, and developers expect secure data exchange between microservices. Two key protocols — OAuth 2.0 (access delegation) and JWT (compact token format) — have become the de facto standard. In this article, we will break down how to implement OAuth 2.0 and JWT authorization in PHP from scratch, using only built-in language features and popular libraries. You will get ready-made code examples for integration with external providers and creating your own authentication system.
1. OAuth 2.0 Basics: Flows and Roles
OAuth 2.0 is an authorization protocol that allows third-party applications to obtain limited access to user resources without transmitting a password. It involves four roles:
- Resource Owner — the user who owns the data.
- Client — your PHP application requesting access.
- Authorization Server — the server that issues tokens (e.g., Google).
- Resource Server — the API that provides data based on the token.
The most popular flow for web applications is the Authorization Code Grant. It is secure because the token is transmitted through the server side, not through the browser. Let's look at the implementation in PHP.
1.1. Application Registration and Obtaining Client ID/Secret
Before writing code, register your application with a provider (e.g., in Google Cloud Console). You will receive client_id and client_secret, and also configure a redirect URI (e.g., https://yoursite.com/callback.php).
1.2. Implementing the Authorization Code Flow in PHP
Let's create two files: login.php (redirect to the authorization server) and callback.php (handle the response).
File login.php
<?phpsession_start();
$client_id = 'YOUR_CLIENT_ID';$redirect_uri = 'https://yoursite.com/callback.php';$state = bin2hex(random_bytes(16)); // CSRF protection$_SESSION['oauth_state'] = $state;
$params = http_build_query([ 'client_id' => $client_id, 'redirect_uri' => $redirect_uri, 'response_type' => 'code', 'scope' => 'openid email profile', 'state' => $state,]);
header('Location: https://accounts.google.com/o/oauth2/v2/auth?' . $params);exit;?>File callback.php
<?phpsession_start();
// Check state (CSRF)if ($_GET['state'] !== $_SESSION['oauth_state']) { die('Error: invalid state');}
$client_id = 'YOUR_CLIENT_ID';$client_secret = 'YOUR_CLIENT_SECRET';$redirect_uri = 'https://yoursite.com/callback.php';
// Exchange code for token$ch = curl_init('https://oauth2.googleapis.com/token');curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 'code' => $_GET['code'], 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'grant_type' => 'authorization_code',]));curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$response = curl_exec($ch);curl_close($ch);
$data = json_decode($response, true);$access_token = $data['access_token'] ?? null;
// Get user profile$ch = curl_init('https://www.googleapis.com/oauth2/v2/userinfo');curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $access_token]);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$userInfo = json_decode(curl_exec($ch), true);curl_close($ch);
echo 'Hello, ' . htmlspecialchars($userInfo['email']);?>Important: Always check the state parameter. It protects against request forgery (CSRF).