Skip to content

Commit

Permalink
Logout via Query Params
Browse files Browse the repository at this point in the history
* Remove "cookieName" configuration and assume "<serviceName>-jwt" as
  cookie name
* Adjust `SetJwtCookieComponent` to handle logout (remove JWT cookie
  and trigger redirect) if `?logout=<serviceName>` query parameter is
  present

Closes: flownative#2
  • Loading branch information
bwaidelich committed Jul 30, 2019
1 parent 869c229 commit 35d1eeb
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 82 deletions.
9 changes: 4 additions & 5 deletions Classes/Authentication/OpenIdConnectProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,11 @@ public function authenticate(TokenInterface $authenticationToken): void
if (!isset($this->options['accountIdentifierTokenValueName'])) {
$this->options['accountIdentifierTokenValueName'] = 'sub';
}
if (!isset($this->options['jwtCookieName'])) {
$this->options['jwtCookieName'] = 'flownative_oidc_jwt';
}
$serviceName = $this->options['serviceName'];
$jwtCookieName = $serviceName . '-jwt';
try {
$jwks = (new OpenIdConnectClient($this->options['serviceName']))->getJwks();
$identityToken = $authenticationToken->extractIdentityTokenFromRequest($this->options['jwtCookieName']);
$jwks = (new OpenIdConnectClient($serviceName))->getJwks();
$identityToken = $authenticationToken->extractIdentityTokenFromRequest($jwtCookieName);
if (!$identityToken->hasValidSignature($jwks)) {
throw new SecurityException(sprintf('Open ID Connect: The identity token provided by the OIDC provider had an invalid signature'), 1561479176);
}
Expand Down
51 changes: 45 additions & 6 deletions Classes/Http/SetJwtCookieComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
namespace Flownative\OpenIdConnect\Client\Http;

use Flownative\OpenIdConnect\Client\Authentication\OpenIdConnectToken;
use Flownative\OpenIdConnect\Client\AuthenticationException;
use Flownative\OpenIdConnect\Client\IdentityToken;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Http\Component\ComponentContext;
use Neos\Flow\Http\Component\ComponentInterface;
use Neos\Flow\Http\Cookie;
use Neos\Flow\Security\Context as SecurityContext;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;

final class SetJwtCookieComponent implements ComponentInterface
Expand Down Expand Up @@ -41,8 +41,12 @@ public function __construct(array $options = null)
*/
public function handle(ComponentContext $componentContext): void
{
if ($this->isLogoutRequest($componentContext)) {
$this->handleLogout($componentContext);
return;
}
if (!$this->securityContext->isInitialized() && !$this->securityContext->canBeInitialized()) {
$this->logger->debug(sprintf('OpenID Connect Client: (%s) Cannot send JWT cookie because the security context could not be initialized.', get_class($this)));
$this->logger->debug(sprintf('OpenID Connect Client: (%s) Cannot send JWT cookie because the security context could not be initialized for service "%s".', get_class($this), $this->options['serviceName']));
return;
}
if (!$this->isOpenIdConnectAuthentication()) {
Expand All @@ -51,14 +55,19 @@ public function handle(ComponentContext $componentContext): void

$account = $this->securityContext->getAccountByAuthenticationProviderName($this->options['authenticationProviderName']);
if ($account === null) {
$this->logger->info(sprintf('OpenID Connect Client: (%s) No Flow account found for %s, removing JWT cookie.', get_class($this), $this->options['authenticationProviderName']));
$this->logger->info(sprintf('OpenID Connect Client: (%s) No Flow account found for %s, removing JWT cookie for service "%s".', get_class($this), $this->options['authenticationProviderName'], $this->options['serviceName']));
$this->removeJwtCookie($componentContext);
return;
}
if ($this->isLogoutRequest($componentContext)) {
$this->removeJwtCookie($componentContext);
$this->logger->info(sprintf('OpenID Connect Client: (%s) Logout requested (via query parameter) removing JWT cookie for service "%s".', get_class($this), $this->options['serviceName']));
return;
}

$identityToken = $account->getCredentialsSource();
if (!$identityToken instanceof IdentityToken) {
$this->logger->error(sprintf('OpenID Connect Client: (%s) No identity token found in credentials source of account %s - could not set JWT cookie.', get_class($this), $account->getAccountIdentifier()));
$this->logger->error(sprintf('OpenID Connect Client: (%s) No identity token found in credentials source of account %s - could not set JWT cookie for service "%s".', get_class($this), $account->getAccountIdentifier(), $this->options['serviceName']));
return;
}

Expand All @@ -78,13 +87,38 @@ private function isOpenIdConnectAuthentication(): bool
return false;
}

/**
* @param ComponentContext $componentContext
* @return bool
*/
private function isLogoutRequest(ComponentContext $componentContext): bool
{
$httpRequest = $componentContext->getHttpRequest();
if (!$httpRequest instanceof ServerRequestInterface) {
return false;
}
$queryParams = $httpRequest->getQueryParams();
return isset($queryParams['logout']) && $queryParams['logout'] === $this->options['serviceName'];
}

/**
* @param ComponentContext $componentContext
* @return void
*/
private function handleLogout(ComponentContext $componentContext): void
{
$this->removeJwtCookie($componentContext);
$this->logger->info(sprintf('OpenID Connect Client: (%s) Logout requested (via query parameter) removing JWT cookie for service "%s".', get_class($this), $this->options['serviceName']));
$componentContext->replaceHttpResponse($componentContext->getHttpResponse()->withHeader('Location', (string)$componentContext->getHttpRequest()->getUri()->withQuery('')));
}

/**
* @param ComponentContext $componentContext
* @param string $jwt
*/
private function setJwtCookie(ComponentContext $componentContext, string $jwt): void
{
$jwtCookie = new Cookie($this->options['cookieName'], $jwt, 0, null, null, '/', $this->options['secureCookie'], false);
$jwtCookie = new Cookie($this->jwtCookieName(), $jwt, 0, null, null, '/', $this->options['secureCookie'], false);
$componentContext->replaceHttpResponse($componentContext->getHttpResponse()->withAddedHeader('Set-Cookie', (string)$jwtCookie));
}

Expand All @@ -93,7 +127,12 @@ private function setJwtCookie(ComponentContext $componentContext, string $jwt):
*/
private function removeJwtCookie(ComponentContext $componentContext): void
{
$emptyJwtCookie = new Cookie($this->options['cookieName'], '', 1, null, null, '/', $this->options['secureCookie'], false);
$emptyJwtCookie = new Cookie($this->jwtCookieName(), '', 1, null, null, '/', $this->options['secureCookie'], false);
$componentContext->replaceHttpResponse($componentContext->getHttpResponse()->withAddedHeader('Set-Cookie', (string)$emptyJwtCookie));
}

private function jwtCookieName(): string
{
return $this->options['serviceName'] . '-jwt';
}
}
2 changes: 1 addition & 1 deletion Configuration/Settings.Http.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ Neos:
'position': 'after setSessionCookie'
component: 'Flownative\OpenIdConnect\Client\Http\SetJwtCookieComponent'
componentOptions:
cookieName: 'flownative_oidc_jwt'
serviceName: 'flownative_oidc'
secureCookie: true
authenticationProviderName: 'Flownative.OpenIdConnect.Client:OidcProvider'
4 changes: 2 additions & 2 deletions Configuration/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Flownative:
# chain:
# 'Flownative.OpenIdConnect.Client:setJwtCookie':
# componentOptions:
# cookieName: 'your-own-cookie-name-jwt'
# # Use the same service name as below - the corresponding JWT will be stored in a cookie named "<serviceName>-jwt"
# serviceName: 'exampleService'

# security:
# authentication:
Expand All @@ -27,7 +28,6 @@ Flownative:
# providerOptions:
# roles: ['Acme.MyPackage:User']
# accountIdentifierTokenValueName: 'inum'
# jwtCookieName: 'your-own-cookie-name-jwt'
# serviceName: 'exampleService'
# token: 'Flownative\OpenIdConnect\Client\Authentication\OpenIdConnectToken'
# requestPatterns:
Expand Down
135 changes: 67 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,67 @@
[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
[![Packagist](https://img.shields.io/packagist/v/flownative/openidconnect-client.svg)](https://packagist.org/packages/flownative/openidconnect-client)
[![Maintenance level: Love](https://img.shields.io/badge/maintenance-%E2%99%A1%E2%99%A1%E2%99%A1-ff69b4.svg)](https://www.flownative.com/en/products/open-source.html)

# OpenID Connect Client for Flow Framework

This [Flow](https://flow.neos.io) package provides an [OpenID Connect](https://openid.net/connect/) client SDK.

Note: this package is at an early stage, breaking changes may be released without further warning ...

## Configuration

Flownative:
OpenIdConnect:
Client:
services: []
exampleService:
options:
discoveryUri: 'https://example.com/.well-known/openid-configuration'
clientId: '@!EDD5.370D.8247.FED9!0001!B1C9.92C1!1008!13DB.54D8.65DE.2761'
clientSecret: 'very-secret'

Neos:
Flow:
http:
chain:
'postprocess':
chain:
'Flownative.OpenIdConnect.Client:setJwtCookie':
componentOptions:
cookieName: 'your-own-cookie-name-jwt'
secureCookie: false

security:
authentication:
providers:
'Flownative.OpenIdConnect.Client:OidcProvider':
label: 'OpenID Connect'
provider: 'Flownative\OpenIdConnect\Client\Authentication\OpenIdConnectProvider'
providerOptions:
roles: ['Acme.MyPackage:User']
accountIdentifierTokenValueName: 'inum'
jwtCookieName: 'your-own-cookie-name-jwt'
serviceName: 'exampleService'
token: 'Flownative\OpenIdConnect\Client\Authentication\OpenIdConnectToken'
requestPatterns:
'Acme.MyPackage:Frontend':
pattern: 'Acme\MyPackage\Security\SiteRequestPattern'
patternOptions:
'siteNodeName': 'mysite'
'matchFrontend': true
entryPoint: 'Flownative\OpenIdConnect\Client\Authentication\OpenIdConnectEntryPoint'
entryPointOptions:
serviceName: 'acmeservice'
scopes: ['inum', 'user_name', 'sap_custno']

## Authentication Flow

Authentication works as follows:

...

## About OpenID Connect

See also:

https://openid.net/specs/openid-connect-basic-1_0.html
https://connect2id.com/learn/openid-connect
[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
[![Packagist](https://img.shields.io/packagist/v/flownative/openidconnect-client.svg)](https://packagist.org/packages/flownative/openidconnect-client)
[![Maintenance level: Love](https://img.shields.io/badge/maintenance-%E2%99%A1%E2%99%A1%E2%99%A1-ff69b4.svg)](https://www.flownative.com/en/products/open-source.html)

# OpenID Connect Client for Flow Framework

This [Flow](https://flow.neos.io) package provides an [OpenID Connect](https://openid.net/connect/) client SDK.

Note: this package is at an early stage, breaking changes may be released without further warning ...

## Configuration

Flownative:
OpenIdConnect:
Client:
services: []
exampleService:
options:
discoveryUri: 'https://example.com/.well-known/openid-configuration'
clientId: '@!EDD5.370D.8247.FED9!0001!B1C9.92C1!1008!13DB.54D8.65DE.2761'
clientSecret: 'very-secret'

Neos:
Flow:
http:
chain:
'postprocess':
chain:
'Flownative.OpenIdConnect.Client:setJwtCookie':
componentOptions:
serviceName: 'acmeservice'
secureCookie: false

security:
authentication:
providers:
'Flownative.OpenIdConnect.Client:OidcProvider':
label: 'OpenID Connect'
provider: 'Flownative\OpenIdConnect\Client\Authentication\OpenIdConnectProvider'
providerOptions:
roles: ['Acme.MyPackage:User']
accountIdentifierTokenValueName: 'inum'
serviceName: 'acmeservice'
token: 'Flownative\OpenIdConnect\Client\Authentication\OpenIdConnectToken'
requestPatterns:
'Acme.MyPackage:Frontend':
pattern: 'Acme\MyPackage\Security\SiteRequestPattern'
patternOptions:
'siteNodeName': 'mysite'
'matchFrontend': true
entryPoint: 'Flownative\OpenIdConnect\Client\Authentication\OpenIdConnectEntryPoint'
entryPointOptions:
serviceName: 'acmeservice'
scopes: ['inum', 'user_name', 'sap_custno']

## Authentication Flow

Authentication works as follows:

...

## About OpenID Connect

See also:

https://openid.net/specs/openid-connect-basic-1_0.html
https://connect2id.com/learn/openid-connect

0 comments on commit 35d1eeb

Please sign in to comment.