Skip to content
Ray Fung edited this page Feb 26, 2026 · 1 revision

OAuth2

The OAuth2 class provides a generic OAuth 2.0 authorization code flow implementation. It handles authorization URL generation, token exchange, token refresh, JWT parsing, and authenticated API requests via cURL.

Overview

The class is provider-agnostic — configure it with any OAuth 2.0 provider (Google, GitHub, Microsoft, etc.) by setting the authorization and token endpoint URLs. It uses the Authorization Code Grant flow with built-in CSRF protection via state tokens.

Constructor

use Razy\OAuth2;

$oauth = new OAuth2(
    'your-client-id',       // OAuth client ID
    'your-client-secret',   // OAuth client secret
    'https://example.com/callback'  // Redirect URI
);

Methods

Method Return Description
setAuthorizeUrl(string $url) OAuth2 Set the authorization endpoint URL
setTokenUrl(string $url) OAuth2 Set the token exchange endpoint URL
setScope(string $scope) OAuth2 Set requested OAuth scopes (space-separated)
setState(?string $state = null) OAuth2 Set CSRF state token (auto-generated if null)
getState() ?string Get current state token
addParam(string $key, string $value) OAuth2 Add extra authorization query parameters
getAuthorizationUrl() string Generate the full authorization redirect URL
getAccessToken(string $code) array Exchange authorization code for access token
refreshAccessToken(string $refreshToken) array Refresh an expired access token
httpGet(string $url, string $accessToken) array Make authenticated GET request to an API
getTokenData() array Get stored token data from last exchange

Static Methods

Method Return Description
OAuth2::validateState(string $received, string $expected) bool Timing-safe CSRF state validation
OAuth2::parseJWT(string $jwt) array Decode JWT payload without signature verification
OAuth2::isJWTExpired(array $claims) bool Check if JWT claims have expired

Configuration

All setter methods return $this for fluent chaining:

use Razy\OAuth2;

$oauth = (new OAuth2($clientId, $clientSecret, $redirectUri))
    ->setAuthorizeUrl('https://accounts.google.com/o/oauth2/v2/auth')
    ->setTokenUrl('https://oauth2.googleapis.com/token')
    ->setScope('openid email profile')
    ->addParam('access_type', 'offline')
    ->addParam('prompt', 'consent');

Step 1: Authorization Redirect

Generate the authorization URL and redirect the user. A CSRF state token is automatically generated if not explicitly set.

// Generate authorization URL (state token auto-created)
$authUrl = $oauth->getAuthorizationUrl();

// Store state in session for CSRF verification
$_SESSION['oauth_state'] = $oauth->getState();

// Redirect the user
header('Location: ' . $authUrl);
exit;

Step 2: Handle Callback

In your callback handler, validate the state token and exchange the authorization code for an access token:

use Razy\OAuth2;

// Validate CSRF state (timing-safe comparison)
if (!OAuth2::validateState($_GET['state'], $_SESSION['oauth_state'])) {
    throw new Exception('Invalid state token — possible CSRF attack');
}

// Exchange code for token
$tokenData = $oauth->getAccessToken($_GET['code']);

// $tokenData contains: access_token, refresh_token, expires_in, etc.
$accessToken  = $tokenData['access_token'];
$refreshToken = $tokenData['refresh_token'] ?? null;

Step 3: API Requests

Use the access token to make authenticated requests:

// Fetch user profile from Google
$profile = $oauth->httpGet(
    'https://www.googleapis.com/oauth2/v2/userinfo',
    $accessToken
);

echo $profile['email'];   // user@example.com
echo $profile['name'];    // John Doe

Refreshing Tokens

When an access token expires, use the refresh token to obtain a new one:

$newTokenData = $oauth->refreshAccessToken($refreshToken);
$newAccessToken = $newTokenData['access_token'];

JWT Utilities

Parse and inspect JWT tokens returned by OpenID Connect providers:

use Razy\OAuth2;

// Parse the ID token (no signature verification)
$claims = OAuth2::parseJWT($tokenData['id_token']);
// → ['sub' => '123', 'email' => 'user@example.com', 'exp' => 1700000000, ...]

// Check expiration
if (OAuth2::isJWTExpired($claims)) {
    // Token has expired — refresh or re-authenticate
}

Note: parseJWT() decodes the payload without verifying the signature. Use it only for extracting claims from tokens already validated by the provider's token endpoint.

Example: GitHub Login

use Razy\OAuth2;

$github = (new OAuth2(
    'github-client-id',
    'github-client-secret',
    'https://myapp.com/auth/github/callback'
))
    ->setAuthorizeUrl('https://github.com/login/oauth/authorize')
    ->setTokenUrl('https://github.com/login/oauth/access_token')
    ->setScope('user:email read:user');

// Redirect to GitHub
$authUrl = $github->getAuthorizationUrl();
$_SESSION['oauth_state'] = $github->getState();

// In callback handler:
$tokens = $github->getAccessToken($_GET['code']);
$user   = $github->httpGet('https://api.github.com/user', $tokens['access_token']);

echo $user['login'];  // GitHub username

Clone this wiki locally