Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logout via Query Params #3

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
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('')));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is a bit magic.. It redirects to the current URI without query params. If other params are required they would be missing. I think we have to revise this part, but without the redirect the account is already authenticated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a defined "afterLogout" URL?

}

/**
* @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