Skip to content

Instantly share code, notes, and snippets.

@ajgarlag
Last active May 28, 2021 15:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ajgarlag/1f84d29ee0e1a92c8878f44a902338cd to your computer and use it in GitHub Desktop.
Save ajgarlag/1f84d29ee0e1a92c8878f44a902338cd to your computer and use it in GitHub Desktop.
Simple trikoder/oauth2-bundle decision flow
{# templates/oauth2/decide.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div>
<a href="{{ allow_uri }}">Allow</a> | <a href="{{ deny_uri }}">Deny</a>
</div>
{% endblock %}
<?php
//src/Controller/DecisionController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\UriSigner;
use App\EventListener\SignedAuthorizationRequestSubscriber;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class DecisionController extends AbstractController
{
/**
* @var UriSigner
*/
private $uriSigner;
/**
* @var string
*/
private $authorizationRoute;
public function __construct(UriSigner $uriSigner, string $authorizationRoute = 'oauth2_authorize')
{
$this->uriSigner = $uriSigner;
$this->authorizationRoute = $authorizationRoute;
}
/**
* @Route("/oauth2/authorize/decide", name="oauth2_decision")
*/
public function decisionAction(Request $request)
{
return $this->render('oauth2/decide.html.twig', [
'allow_uri' => $this->buildDecidedUri($request, true),
'deny_uri' => $this->buildDecidedUri($request, false),
]);
}
private function buildDecidedUri(Request $request, bool $allowed)
{
$currentQuery = $request->query->all();
$decidedQuery = array_merge($currentQuery, [SignedAuthorizationRequestSubscriber::ATTRIBUTE_DECISION => $this->buildDecisionValue($allowed)]);
$decidedUri = $this->generateUrl($this->authorizationRoute, $decidedQuery);
return $this->uriSigner->sign($decidedUri);
}
private function buildDecisionValue(bool $allowed): string
{
return $allowed ? SignedAuthorizationRequestSubscriber::ATTRIBUTE_DECISION_ALLOW : '';
}
}
<?php
//src/EventListener/SignedAuthorizationRequestSubscriber.php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\UriSigner;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEvent;
use Trikoder\Bundle\OAuth2Bundle\Model\Scope;
use Trikoder\Bundle\OAuth2Bundle\OAuth2Events;
use Trikoder\Bundle\OAuth2Bundle\OAuth2Grants;
class SignedAuthorizationRequestSubscriber implements EventSubscriberInterface
{
const ATTRIBUTE_DECISION = 'decision';
const ATTRIBUTE_DECISION_ALLOW = 'allow';
/**
* @var UriSigner
*/
private $uriSigner;
/**
* @var RequestStack
*/
private $requestStack;
/**
* @var UrlGeneratorInterface
*/
private $urlGenerator;
/**
* @var string
*/
private $decisionRoute;
public function __construct(UriSigner $uriSigner, RequestStack $requestStack, UrlGeneratorInterface $urlGenerator, string $decisionRoute)
{
$this->uriSigner = $uriSigner;
$this->requestStack = $requestStack;
$this->urlGenerator = $urlGenerator;
$this->decisionRoute = $decisionRoute;
}
public function processSignedAuthorizationRequest(AuthorizationRequestResolveEvent $event): void
{
if (null === $request = $this->requestStack->getMasterRequest()) {
return;
}
$currentUri = $request->getRequestUri();
if (!$this->uriSigner->check($currentUri)) {
return;
}
if (!$this->canResolveAuthorizationRequest($event, $request)) {
return;
}
$event->resolveAuthorization($this->isAuthorizationAllowed($request));
}
private function canResolveAuthorizationRequest(AuthorizationRequestResolveEvent $event, Request $request)
{
if (!$request->query->has(self::ATTRIBUTE_DECISION)) {
return false;
}
if ($request->query->get('client_id') !== $event->getClient()->getIdentifier()) {
return false;
}
if ($request->query->get('response_type') !== $this->getResponseType($event)) {
return false;
}
if ($request->query->get('redirect_uri') !== $event->getRedirectUri()) {
return false;
}
if ($request->query->get('scope') !== $this->getScope($event)) {
return false;
}
return true;
}
private function getResponseType(AuthorizationRequestResolveEvent $event): string
{
switch ($event->getGrantTypeId()) {
case OAuth2Grants::AUTHORIZATION_CODE:
return 'code';
case OAuth2Grants::IMPLICIT:
return 'token';
default:
return $event->getGrantTypeId();
}
}
private function getScope(AuthorizationRequestResolveEvent $event): ?string
{
$scopes = $event->getScopes();
if (empty($scopes)) {
return null;
}
return implode(' ', array_map('strval', $scopes));
}
private function isAuthorizationAllowed(Request $request): bool
{
return $request->get(self::ATTRIBUTE_DECISION) === self::ATTRIBUTE_DECISION_ALLOW;
}
public function redirectToDecisionRoute(AuthorizationRequestResolveEvent $event): void
{
$params = [
'client_id' => $event->getClient()->getIdentifier(),
'response_type' => $this->getResponseType($event),
];
if (null !== $redirectUri = $event->getRedirectUri()) {
$params['redirect_uri'] = $redirectUri;
}
if (null !== $state = $event->getState()) {
$params['state'] = $state;
}
$scope = $this->getScope($event);
if (null !== $scope) {
$params['scope'] = $scope;
}
$event->setResponse(
new RedirectResponse(
$this->urlGenerator->generate($this->decisionRoute, $params)
)
);
}
public static function getSubscribedEvents(): array
{
return [
OAuth2Events::AUTHORIZATION_REQUEST_RESOLVE => [
['processSignedAuthorizationRequest', 100],
['redirectToDecisionRoute', 50],
],
];
}
}
@gustvao
Copy link

gustvao commented Sep 22, 2019

hey @ajgarlag , what did you put in your services.yaml to make this work?

I am receiving the following error, any color on what may be causing it?

Cannot autowire service "App\Application\Service\OAuth2\SignedAuthorizationRequestSubscriber": argument "$uriSigner" of method "__construct()" references class "Symfony\Component\HttpKernel\UriSigner" but no such service exists. You should maybe alias this class to the existing "uri_signer" service.```

@ajgarlag
Copy link
Author

ajgarlag commented Sep 23, 2019

You have two options:

@gustvao
Copy link

gustvao commented Sep 23, 2019

Ok, that worked.
this is what I added to services.yaml

App\Application\Service\OAuth2\SignedAuthorizationRequestSubscriber:
        arguments:
            $uriSigner: 'bla'
            $decisionRoute: 'http://127.0.0.1:8000'
        tags:
            - { name: kernel.event_listener, event: trikoder.oauth2.authorization_request_resolve, method: processSignedAuthorizationRequest }

However when I hit /authorize method processSignedAuthorizationRequest never gets called.

If you could provide the working example it would be awesome.

Tks again

@gustvao
Copy link

gustvao commented Sep 23, 2019

Sorry, that $uriSigner: 'bla' is not working =/

"Argument 1 passed to App\\Application\\Service\\OAuth2\\SignedAuthorizationRequestSubscriber::__construct() must be an instance of Symfony\\Component\\HttpKernel\\UriSigner, string given, called in //var/cache/dev/ContainerHKNUUVZ/getSignedAuthorizationRequestSubscriberService.php on line 11"
}

@zhukovsergei
Copy link

Hi, Why you have duplicate keys in array 151, 152 lines on SignedAuthorizationRequestSubscriber?

@ajgarlag
Copy link
Author

@zhukovsergei It's a bug, it should be:

return [
    OAuth2Events::AUTHORIZATION_REQUEST_RESOLVE => [
        ['processSignedAuthorizationRequest', 100],
        ['redirectToDecisionRoute', 50],
    ],
];

@fishmandev
Copy link

fishmandev commented Nov 10, 2020

@ajgarlag, https://gist.github.com/ajgarlag/1f84d29ee0e1a92c8878f44a902338cd#file-signedauthorizationrequestsubscriber-php-L141
Argument must implement interface Psr\Http\Message\ResponseInterface, instead of Symfony\Component\HttpFoundation\RedirectResponse

Symfony: 5.1.18

@fishmandev
Copy link

The question is closed.
$this->container->get('security.token_storage')->getToken()->getUser()

@mssoylu
Copy link

mssoylu commented Feb 3, 2021

@fishmandev how did you fix it?

@ajgarlag do you have any idea?

I guess it's about PSR standart for Symfony5. $event only accept PSR ResponseInterface but not HTTPFoundation based RedirectResponse.

@vatoer
Copy link

vatoer commented May 28, 2021

to solve error
Argument must implement interface Psr\Http\Message\ResponseInterface, instead of Symfony\Component\HttpFoundation\RedirectResponse

do
composer require nyholm/psr7

add this on the top

//src/EventListener/SignedAuthorizationRequestSubscriber.ph
use Nyholm\Psr7\Response;

change this line
https://gist.github.com/ajgarlag/1f84d29ee0e1a92c8878f44a902338cd#file-signedauthorizationrequestsubscriber-php-L141

$event->setResponse(
            new RedirectResponse(
                $this->urlGenerator->generate($this->decisionRoute, $params)
            )
        );

to

$url = $this->urlGenerator->generate($this->decisionRoute, $params);
$headers = ["Location"=>$url];
$response = new Response(301,$headers);
$event->setResponse($response);

hope this help, even a bit late @mssoylu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment