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

Add tokens exchange functionality #66

Closed
wants to merge 8 commits into from

Conversation

fritjofbohm
Copy link
Contributor

@fritjofbohm fritjofbohm commented Aug 9, 2024

Hi all,
this PR adds a basic token exchange functionality (see https://www.rfc-editor.org/rfc/rfc8693) to this package.

With this functionality, this module can request a new token on behalf of an authenticated user, but with a different audience or different scope.

In my use case, there's an internal API that also uses SSO to identify the user. The token issued by the SSO for my Symfony app doesn't work for the API (different audience/scope). This is where the token exchange comes in: The app can ask the SSO provider to issue a new token that can authenticate the user against the API.

This whole procedure is not overly complicated, but there are some things in this module's core that need to be modified for it. The changes should not break any public signatures, so this could be a new minor version. I'm very open to any suggestions - please let me know what you think.


Requesting an exchange token is basically the same as requesting an authorization_code or refresh_token, only that there are some additional params in the token endpoint call. The response is a little different though, so there are some reefs I needed to navigate around.

  • OidcClient->requestTokens() has a few additional parameters.
  • The function's return type is changed from OidcTokens to stdClass. This is because the exchange tokens response does not contain an ID token, and calling the original OidcTokens constructor without ID token would fail.
  • Constructing the OidcTokens now happens in authenticate() and refreshTokens().
  • There's a new public method OidcClient->exchangeTokens() (and also in the client interface). This method will initiate the tokens exchange.
  • To circumvent the problem of the non-present ID token, exchangeTokens() uses the new class OidcExchangeTokens instead of OidcTokens.

The tokens exchange functionality could for example be used like this:

<?php

namespace App\Security;

use Drenso\OidcBundle\Model\OidcTokens;
use Drenso\OidcBundle\OidcClientInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class OidcTokenExchange
{
    public function __construct(
        protected Security $security,
        protected OidcClientInterface $oidcClient
    ) {}

    public function getExchangeToken(string $targetScope): string
    {
        if ($this->security->getUser() && $this->security->getToken()) {
            // You should perhaps add a session/caching mechanism to avoid too many exchange requests
            $tokens = $this->performTokenExchange($targetScope);

            if (null !== $tokens) {
                return $tokens->getAccessToken();
            }
        }

        throw new AccessDeniedException('This only works with an authenticated User');
    }

    protected function performTokenExchange(string $targetScope): ?OidcTokens
    {
        $token = $this->security->getToken();

        if (null !== $token) {
            $authData = $token->getAttribute('auth_data');

            if ($authData instanceof OidcTokens) {
                $accessToken = $authData->getAccessToken();

                return $this->oidcClient->exchangeTokens(
                    $accessToken,
                    targetScope: $targetScope
                );
            }
        }

        return null;
    }
}

Copy link
Member

@bobvandevijver bobvandevijver left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, great work. I have commented with an alternative approach to solve the missing id token issue, wdyt about that?

src/OidcClient.php Outdated Show resolved Hide resolved
src/Model/OidcExchangeTokens.php Outdated Show resolved Hide resolved
src/OidcClient.php Outdated Show resolved Hide resolved
src/OidcClient.php Show resolved Hide resolved
src/OidcClient.php Outdated Show resolved Hide resolved
src/OidcClient.php Outdated Show resolved Hide resolved
src/OidcClient.php Outdated Show resolved Hide resolved
@bobvandevijver
Copy link
Member

Great addition @fritjofbohm, thank you! Merged with 47bc477 👍🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants