Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,34 @@ We can cache the OpenId Configuration: it's a list of endpoints we require to Ke

If you activate it, *remember to flush the cache* when change the realm or url.

## Security Configuration

For enhanced security, this package supports multiple ways to load OpenID configuration, avoiding public exposure of the `.well-known/openid-configuration` endpoint.

### OpenID Configuration Sources

You can configure how the OpenID configuration is loaded using the `KEYCLOAK_OPENID_SOURCE` environment variable:

**Option 1: Remote (Default - Less Secure)**
```env
KEYCLOAK_OPENID_SOURCE=remote
```
Loads configuration from the public `.well-known/openid-configuration` endpoint.

**Option 2: Local File (Recommended)**
```env
KEYCLOAK_OPENID_SOURCE=local
KEYCLOAK_OPENID_LOCAL_PATH=/absolute/path/to/openid-configuration.json
```
Loads configuration from a local JSON file.

**Option 3: Base64 Encoded (Highly Secure)**
```env
KEYCLOAK_OPENID_SOURCE=base64
KEYCLOAK_OPENID_BASE64_CONFIG=eyJpc3N1ZXIiOiJodHRwczovL3lvdXIta2V5Y2xvYWstc2VydmVyLmNvbS9yZWFsbXMveW91ci1yZWFsbSIsIi4uLn0=
```
Stores configuration as base64-encoded JSON in environment variables.

Just add the options you would like as an array to the" to "Just add the options you would like to guzzle_options array on keycloak-web.php config file. For example:

## Laravel Auth
Expand Down
26 changes: 26 additions & 0 deletions config/keycloak-web.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,32 @@
*/
'cache_openid' => env('KEYCLOAK_CACHE_OPENID', false),

/**
* OpenID Configuration Source
*
* Options:
* - 'remote': Fetch from the public .well-known endpoint (default, less secure)
* - 'local': Load from a local file path
* - 'base64': Load from base64 encoded configuration in env/config
*/
'openid_source' => env('KEYCLOAK_OPENID_SOURCE', 'remote'),

/**
* Local OpenID Configuration File Path
*
* Used when openid_source is 'local'
* Path to a local JSON file containing the OpenID configuration
*/
'openid_local_path' => env('KEYCLOAK_OPENID_LOCAL_PATH', storage_path('app/keycloak/openid-configuration.json')),

/**
* Base64 Encoded OpenID Configuration
*
* Used when openid_source is 'base64'
* Base64 encoded JSON string of the OpenID configuration
*/
'openid_base64_config' => env('KEYCLOAK_OPENID_BASE64_CONFIG', null),

/**
* Page to redirect after callback if there's no "intent"
*
Expand Down
104 changes: 97 additions & 7 deletions src/Services/KeycloakService.php
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ protected function getOpenIdValue($key)
*/
protected function getOpenIdConfiguration()
{
$cacheKey = 'keycloak_web_guard_openid-' . $this->realm . '-' . md5($this->baseUrl);
$cacheKey = 'keycloak_web_guard_openid-' . $this->realm . '-' . hash('sha256', $this->baseUrl);

// From cache?
if ($this->cacheOpenid) {
Expand All @@ -522,7 +522,39 @@ protected function getOpenIdConfiguration()
}
}

// Request if cache empty or not using
// Get configuration based on source type
$source = Config::get('keycloak-web.openid_source', 'remote');
$configuration = [];

switch ($source) {
case 'local':
$configuration = $this->loadOpenIdFromLocal();
break;
case 'base64':
$configuration = $this->loadOpenIdFromBase64();
break;
case 'remote':
default:
$configuration = $this->loadOpenIdFromRemote();
break;
}

// Save cache
if ($this->cacheOpenid && !empty($configuration)) {
Cache::put($cacheKey, $configuration);
}

return $configuration;
}

/**
* Load OpenID configuration from remote .well-known endpoint
*
* @return array
* @throws Exception
*/
protected function loadOpenIdFromRemote()
{
$url = $this->baseUrl . '/realms/' . $this->realm;
$url = $url . '/.well-known/openid-configuration';

Expand All @@ -538,15 +570,73 @@ protected function getOpenIdConfiguration()
} catch (GuzzleException $e) {
$this->logException($e);

throw new Exception('[Keycloak Error] It was not possible to load OpenId configuration: ' . $e->getMessage());
throw new Exception('[Keycloak Error] It was not possible to load OpenId configuration from remote: ' . $e->getMessage());
}

// Save cache
if ($this->cacheOpenid) {
Cache::put($cacheKey, $configuration);
return $configuration;
}

/**
* Load OpenID configuration from local file
*
* @return array
* @throws Exception
*/
protected function loadOpenIdFromLocal()
{
$localPath = Config::get('keycloak-web.openid_local_path');

if (empty($localPath) || !file_exists($localPath)) {
throw new Exception('[Keycloak Error] Local OpenID configuration file not found: ' . $localPath);
}

return $configuration;
try {
$content = file_get_contents($localPath);
$configuration = json_decode($content, true);

if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('[Keycloak Error] Invalid JSON in local OpenID configuration file: ' . json_last_error_msg());
}

return $configuration;
} catch (Exception $e) {
Log::error('[Keycloak Service] Failed to load local OpenID configuration: ' . $e->getMessage());
throw new Exception('[Keycloak Error] It was not possible to load OpenId configuration from local file: ' . $e->getMessage());
}
}

/**
* Load OpenID configuration from base64 encoded config
*
* @return array
* @throws Exception
*/
protected function loadOpenIdFromBase64()
{
$base64Config = Config::get('keycloak-web.openid_base64_config');

if (empty($base64Config)) {
throw new Exception('[Keycloak Error] Base64 OpenID configuration is not set in config');
}

try {
$decoded = base64_decode($base64Config, true);

if ($decoded === false) {
throw new Exception('[Keycloak Error] Invalid base64 encoding in OpenID configuration');
}

$configuration = json_decode($decoded, true);

if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('[Keycloak Error] Invalid JSON in base64 OpenID configuration: ' . json_last_error_msg());
}

return $configuration;
} catch (Exception $e) {
Log::error('[Keycloak Service] Failed to load base64 OpenID configuration: ' . $e->getMessage());
throw new Exception('[Keycloak Error] It was not possible to load OpenId configuration from base64: ' . $e->getMessage());
}
}

/**
Expand Down
Loading