Skip to content
Snippets Groups Projects
Commit 34465715 authored by Sigmund Augdal's avatar Sigmund Augdal
Browse files

Added a generic OpenID Connect Auth module

This module extends the OAuth2 Auth module with the following features:
 * claims from the returned id token are added as attributes prefixed with `id_token`
 * The raw id token is added as AuthData
 * Support for the simplesamlphp ForceAuth and isPassive parameters to the authenticate call (mapping to prompt: login and prompt: none parameters in openid connect)
 * Support for generic run-time parameters to the authenticate call. Parameters with the `oidc:` prefix are passed to the authorization request (e.g $as->authenticate(['oidc:acr_values' => 'Level4']) will pass `acr_values=Level4` to the authorization request)
 * Support for RP initiated logout as per https://openid.net/specs/openid-connect-session-1_0.html section 5
parent 815dfb15
No related branches found
No related tags found
No related merge requests found
<?php
namespace SimpleSAML\Module\authoauth2\Auth\Source;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Token\AccessToken;
use SimpleSAML\Auth;
use SimpleSAML\Logger;
use SimpleSAML\Module;
use SimpleSAML\Utils\HTTP;
/**
* Authentication source to authenticate with a generic OpenID Connect idp
*
* @package SimpleSAML\Module\authoauth2
*/
class OpenIDConnect extends \SimpleSAML\Module\authoauth2\Auth\Source\OAuth2
{
/** String used to identify our states. */
const STAGE_LOGOUT = 'authouath2:logout';
/**
* Convert values from the state parameter of the authenticate call into options to the authorization request.
*
* Any parameter prefixed with oidc: are added (without the prefix), in
* addition isPassive and ForceAuthn are converted into prompt=none and
* prompt=login respectively
*
* @param array $state
* @return array
*/
protected function getAuthorizeOptionsFromState(&$state)
{
$result = [];
foreach ($state as $key => $value) {
if (strpos($key, 'oidc:') === 0) {
$result[substr($key, 5)] = $value;
}
}
if (array_key_exists('ForceAuthn', $state) && $state['ForceAuthn']) {
$result['prompt'] = 'login';
}
if (array_key_exists('isPassive', $state) && $state['isPassive']) {
$result['prompt'] = 'none';
}
return $result;
}
/**
* This method is overriding the default empty implementation to parse attributes received in the id_token, and
* place them into the attributes array.
*
* @inheritdoc
*/
protected function postFinalStep(AccessToken $accessToken, AbstractProvider $provider, &$state)
{
$prefix = $this->getAttributePrefix();
$id_token = $accessToken->getValues()['id_token'];
$state['Attributes'] = array_merge($this->convertResourceOwnerAttributes(
$this->extraIdTokenAttributes($id_token),
$prefix . 'id_token' . '.'
), $state['Attributes']);
$state['id_token'] = $id_token;
$state['PersistentAuthData'][] = 'id_token';
$state['LogoutState'] = ['id_token' => $id_token];
}
/**
* Log out from upstream idp if possible
*
* @param array &$state Information about the current logout operation.
* @return void
*/
public function logout(&$state)
{
$providerLabel = $this->getLabel();
$endSessionEndpoint = $this->config->getString('urlEndSession', null);
if (!$endSessionEndpoint) {
Logger::debug("authoauth2: $providerLabel No urlEndSession configured, not doing anything for logout");
return;
}
if (!array_key_exists('id_token', $state)) {
Logger::debug("authoauth2: $providerLabel No id_token in state, not doing anything for logout");
return;
}
$id_token = $state['id_token'];
$postLogoutUrl = $this->config->getString('postLogoutRedirectUri', null);
if (!$postLogoutUrl) {
$postLogoutUrl = Module::getModuleURL('authoauth2/loggedout.php');
}
// We are going to need the authId in order to retrieve this authentication source later, in the callback
$state[self::AUTHID] = $this->getAuthId();
$stateID = \SimpleSAML\Auth\State::saveState($state, self::STAGE_LOGOUT);
$endSessionURL = HTTP::addURLParameters($endSessionEndpoint, [
'id_token_hint' => $id_token,
'post_logout_redirect_uri' => $postLogoutUrl,
'state' => self::STATE_PREFIX . '-' . $stateID,
]);
HTTP::redirectTrustedURL($endSessionURL);
}
}
<?php
/**
* Created by PhpStorm.
* User: patrick
* Date: 12/21/17
* Time: 3:26 PM
*/
namespace SimpleSAML\Module\authoauth2;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use SimpleSAML\Logger;
use SimpleSAML\Module;
use SimpleSAML\Module\authoauth2\Auth\Source\OAuth2;
use SimpleSAML\Module\authoauth2\Auth\Source\OpenIDConnect;
use SimpleSAML\Utils\HTTP;
class OIDCLogoutHandler
{
private $expectedStageState = OpenIDConnect::STAGE_LOGOUT;
private $expectedStateAuthId = OAuth2::AUTHID;
private $expectedPrefix = OAuth2::STATE_PREFIX . '-';
/**
* Look at the state parameter returned by the OpenID Connect server and determine if we can handle it;
* @return bool true if response can be handled by this module
*/
public function canHandleResponseFromRequest(array $request)
{
return strpos(@$request['state'], $this->expectedPrefix) === 0;
}
/**
* Handle an OAuth2 response.
*/
public function handleResponse()
{
$this->handleResponseFromRequest($_REQUEST);
}
public function handleResponseFromRequest(array $request)
{
Logger::debug('authoauth2: logout request=' . var_export($request, true));
if (!$this->canHandleResponseFromRequest($request)) {
// phpcs:ignore Generic.Files.LineLength.TooLong
throw new \SimpleSAML\Error\BadRequest('Either missing state parameter on OpenID Connect logout callback, or cannot be handled by authoauth2');
}
$stateIdWithPrefix = $request['state'];
$stateId = substr($stateIdWithPrefix, strlen($this->expectedPrefix));
//TODO: decide how no-state errors should be handled?
// Likely cause is user clicked back button (state was already consumed and removed) or session expired
$state = \SimpleSAML\Auth\State::loadState($stateId, $this->expectedStageState);
// Find authentication source
if (!array_key_exists($this->expectedStateAuthId, $state)) {
throw new \SimpleSAML\Error\BadRequest('No authsource id data in state for ' . $this->expectedStateAuthId);
}
$sourceId = $state[$this->expectedStateAuthId];
/**
* @var OAuth2 $source
*/
$source = \SimpleSAML\Auth\Source::getById($sourceId, OpenIDConnect::class);
if ($source === null) {
throw new \SimpleSAML\Error\BadRequest('Could not find authentication source with id ' . $sourceId);
}
\SimpleSAML\Auth\Source::completeLogout($state);
}
}
<?php
$handler = new \SimpleSAML\Module\authoauth2\OIDCLogoutHandler();
$handler->handleResponse();
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment