This guide provides PHP code snippets for authenticating users with Keycloak using different approaches.
If you are using PHP with Apache and the mod_auth_openidc module to authenticate users with Keycloak, you can use the following PHP code to handle logout:
<?php
// start session
session_start();
// unset session variables
$_SESSION = array();
// destroy session
session_destroy();
// clear Apache auth_oidc cookie
if (isset($_SERVER['HTTP_COOKIE'])) {
$cookies = explode(';', $_SERVER['HTTP_COOKIE']);
foreach($cookies as $cookie) {
$parts = explode('=', $cookie);
$name = trim($parts[0]);
if ($name == 'auth_oidc') {
setcookie($name, '', time() - 3600, '/');
}
}
}
// redirect to Keycloak logout URL
$logout_url = 'https://your-keycloak-server/realms/your-realm/protocol/openid-connect/logout'; // Updated path for Keycloak 17+
$redirect_uri = 'https://your-redirect-uri';
$logout_redirect_url = $logout_url . '?redirect_uri=' . urlencode($redirect_uri);
header('Location: ' . $logout_redirect_url);
exit;
?>
This code first destroys the PHP session, which will log the user out of any application-specific sessions. Then it clears the auth_oidc cookie, which will log the user out of the Apache session. Finally, it redirects the user to the Keycloak logout URL with a redirect_uri parameter that specifies where to redirect the user after logout.
To automatically log out the user’s session when they log out of Keycloak, you can use the Keycloak OpenID Connect (OIDC) end_session_endpoint to revoke the user’s access token and redirect them to the PHP logout script.
Here’s a PHP code snippet that uses the Keycloak end_session_endpoint:
<?php
// start session
session_start();
// clear Apache auth_oidc cookie
if (isset($_SERVER['HTTP_COOKIE'])) {
$cookies = explode(';', $_SERVER['HTTP_COOKIE']);
foreach($cookies as $cookie) {
$parts = explode('=', $cookie);
$name = trim($parts[0]);
if ($name == 'auth_oidc') {
setcookie($name, '', time() - 3600, '/');
}
}
}
// redirect to Keycloak end session endpoint
$end_session_url = 'https://your-keycloak-server/realms/your-realm/protocol/openid-connect/logout'; // Updated path for Keycloak 17+
$redirect_uri = 'https://your-redirect-uri';
$end_session_redirect_url = $end_session_url . '?id_token_hint=' . $_SESSION['id_token'] . '&post_logout_redirect_uri=' . urlencode($redirect_uri);
header('Location: ' . $end_session_redirect_url);
exit;
?>
Yes! You can implement authentication and authorization using Keycloak without the Apache module. Here are several approaches:
The most popular approach is using the stevenmaguire/oauth2-keycloak library which extends the league/oauth2-client:
composer require stevenmaguire/oauth2-keycloak
<?php
require_once 'vendor/autoload.php';
use Stevenmaguire\OAuth2\Client\Provider\Keycloak;
$provider = new Keycloak([
'authServerUrl' => 'https://your-keycloak-server',
'realm' => 'your-realm',
'clientId' => 'your-client-id',
'clientSecret' => 'your-client-secret',
'redirectUri' => 'https://your-redirect-uri/callback'
]);
if (!isset($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $provider->getState();
header('Location: ' . $authUrl);
exit;
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
exit('Invalid state');
} else {
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
// Optional: Now you have a token you can look up a user's profile
try {
// We got an access token, let's now get the user's details
$user = $provider->getResourceOwner($token);
// Use these details to create a new profile
printf('Hello %s!', $user->getName());
// Store token in session for later use
$_SESSION['access_token'] = $token->getToken();
} catch (Exception $e) {
exit('Failed to get resource owner: ' . $e->getMessage());
}
}
?>
For more control, you can implement the OpenID Connect flow manually:
<?php
session_start();
class KeycloakAuth {
private $config;
public function __construct($config) {
$this->config = $config;
}
public function getLoginUrl() {
$params = [
'response_type' => 'code',
'client_id' => $this->config['client_id'],
'redirect_uri' => $this->config['redirect_uri'],
'scope' => 'openid profile email',
'state' => bin2hex(random_bytes(16))
];
$_SESSION['oauth_state'] = $params['state'];
return $this->config['auth_server_url'] . '/realms/' . $this->config['realm'] .
'/protocol/openid-connect/auth?' . http_build_query($params);
}
public function handleCallback() {
if (!isset($_GET['code']) || !isset($_GET['state']) || $_GET['state'] !== $_SESSION['oauth_state']) {
throw new Exception('Invalid OAuth callback');
}
// Exchange authorization code for access token
$tokenUrl = $this->config['auth_server_url'] . '/realms/' . $this->config['realm'] .
'/protocol/openid-connect/token';
$data = [
'grant_type' => 'authorization_code',
'client_id' => $this->config['client_id'],
'client_secret' => $this->config['client_secret'],
'redirect_uri' => $this->config['redirect_uri'],
'code' => $_GET['code']
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $tokenUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
$response = curl_exec($ch);
$tokenData = json_decode($response, true);
curl_close($ch);
if (!$tokenData || isset($tokenData['error'])) {
throw new Exception('Failed to get access token');
}
// Store tokens in session
$_SESSION['access_token'] = $tokenData['access_token'];
$_SESSION['refresh_token'] = $tokenData['refresh_token'];
$_SESSION['id_token'] = $tokenData['id_token'];
return $tokenData;
}
public function getUserInfo() {
if (!isset($_SESSION['access_token'])) {
throw new Exception('Not authenticated');
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->config['auth_server_url'] . '/realms/' . $this->config['realm'] .
'/protocol/openid-connect/userinfo');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $_SESSION['access_token']
]);
$response = curl_exec($ch);
$userInfo = json_decode($response, true);
curl_close($ch);
return $userInfo;
}
public function logout() {
if (isset($_SESSION['access_token'])) {
$logoutUrl = $this->config['auth_server_url'] . '/realms/' . $this->config['realm'] .
'/protocol/openid-connect/logout';
$params = [
'post_logout_redirect_uri' => $this->config['redirect_uri'],
'id_token_hint' => $_SESSION['id_token'] ?? ''
];
session_destroy();
header('Location: ' . $logoutUrl . '?' . http_build_query($params));
exit;
}
}
}
// Usage example:
$config = [
'auth_server_url' => 'https://your-keycloak-server',
'realm' => 'your-realm',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'redirect_uri' => 'https://your-redirect-uri/callback'
];
$auth = new KeycloakAuth($config);
if (isset($_GET['logout'])) {
$auth->logout();
}
if (!isset($_SESSION['access_token'])) {
if (!isset($_GET['code'])) {
// Redirect to Keycloak login
header('Location: ' . $auth->getLoginUrl());
exit;
} else {
// Handle callback
try {
$auth->handleCallback();
} catch (Exception $e) {
die('Authentication failed: ' . $e->getMessage());
}
}
}
// User is now authenticated, get user info
try {
$userInfo = $auth->getUserInfo();
echo '<h1>Welcome, ' . htmlspecialchars($userInfo['name'] ?? $userInfo['preferred_username']) . '!</h1>';
echo '<a href="?logout=1">Logout</a>';
} catch (Exception $e) {
echo 'Failed to get user info: ' . $e->getMessage();
}
?>
Starting with Keycloak 17, the default distribution is based on Quarkus instead of WildFly. This affects the URL paths:
/auth/realms/.../realms/... (the /auth prefix is removed)Make sure to update your URLs accordingly when using Keycloak 17+.
state parameter to prevent CSRF attacksIn Keycloak, create a client with the following settings:
openid-connectconfidential (for server-side apps) or public (for SPA)+ or specific origins for CORSFor production, consider using environment variables for sensitive configuration:
$config = [
'auth_server_url' => getenv('KEYCLOAK_SERVER_URL'),
'realm' => getenv('KEYCLOAK_REALM'),
'client_id' => getenv('KEYCLOAK_CLIENT_ID'),
'client_secret' => getenv('KEYCLOAK_CLIENT_SECRET'),
'redirect_uri' => getenv('KEYCLOAK_REDIRECT_URI')
];
These examples provide different approaches to integrate Keycloak authentication with PHP applications, from using Apache modules to pure PHP implementations.