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

Make credentials accessible in AuthenticatorInterface#createAuthenticatedToken() #30372

Open
secretpow opened this Issue Feb 24, 2019 · 0 comments

Comments

Projects
None yet
2 participants
@secretpow
Copy link

commented Feb 24, 2019

Description
Today I tried to convert my old simple_preauth authenticator to use the new Guard system for API key authentication.

While implementing my new Guard authenticator, I ran into a problem: I need access to the credentials (i.e. the API key) in AuthenticatorInterface#createAuthenticatedToken(). However, createAuthenticatedToken() only has the $user and a $providerKey as parameters.

Ideally, the credentials would be passed to createAuthenticatedToken() just like they are passed to getUser() or checkCredentials(), but I understand that might not be possible while keeping perfect BC. Perhaps there is another nice way to do this?

My use case is the following: I have a App\Entity\User entity, implementing UserInterface. A User can have multiple ApiKeys that the user can use to authenticate with the API. An ApiKey has an accessLevel, which can be either read or write. Depending on the accessLevel, my authenticator would grant different roles which could then be used for access control.

With my old simple_preauth authenticator, I had the following:

<?php
 
// ...
 
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
    // ...
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        if (!($userProvider instanceof ApiKeyUserProvider))
            throw new \InvalidArgumentException(sprintf('The user provider must be an instance of ApiKeyUserProvider (%s given).', get_class($userProvider)));
 
        // Check if the API key is valid
        $key = $token->getCredentials();
        $apiKey = $userProvider->getApiKey($key);
        if ($apiKey === null)
            throw new AuthenticationException(sprintf('API key "%s" is invalid.', $key));
 
        // Remember when this API key was last used
        $apiKey->setLastUsed(new \DateTime());
        $this->doctrine->getManagerForClass(ApiKey::class)->flush();
 
        // Load the user that owns this API key
        $username = $apiKey->getUser()->getUsername();
        $user = $userProvider->loadUserByUsername($username);
        $roles = $user->getRoles();
 
        // Grant additional roles depending on the API key access level
        if ($apiKey->getAccessLevel() === ApiKey::ACCESS_LEVEL_WRITE)
            $roles[] = 'ROLE_API_WRITE';
        elseif ($apiKey->getAccessLevel() === ApiKey::ACCESS_LEVEL_READ)
            $roles[] = 'ROLE_API_READ';
 
        return new ApiKeyToken($user, $key, $providerKey, $roles, $apiKey);
    }
    // ...
}

As you can see, I can grant different roles depending on the accessLevel of the ApiKey. This makes it easy to secure the API controllers; simply require ROLE_API_READ for read-only methods, or ROLE_API_WRITE for methods that need write access.

I can also store the ApiKey in the authentication token, which allows me easy access to the ApiKey in my controllers, which allows me to log which API key was used to perform a certain action.

I can't do either of these with Guard, because in createAuthenticatedToken(), where the token should be created, I don't have access to the ApiKey any more. I also don't have access to the request (where I originally extract the API key from, in getCredentials()).

Example
This is what I imagine it to look like:

class ApiKeyAuthenticator implements AuthenticatorInterface
{
    // ...
    public function createAuthenticatedToken(UserInterface $user, $providerKey, $credentials)
    {
        $apiKey = $this->em->getRepository(ApiKey::class)->findOneByKey($credentials);

        // Remember the last usage date
        $apiKey->setLastUsed(new \DateTime());
        $this->em->flush();

        // Grant additional roles depending on the API key access level
        $roles = $user->getRoles();
        if ($apiKey->getAccessLevel() === ApiKey::ACCESS_LEVEL_WRITE)
            $roles[] = 'ROLE_API_WRITE';
        elseif ($apiKey->getAccessLevel() === ApiKey::ACCESS_LEVEL_READ)
            $roles[] = 'ROLE_API_READ';

        return new ApiKeyToken($user, $credentials, $providerKey, $roles, $apiKey);
    }
    // ...
}

The extra $credentials parameter allows me to fetch the ApiKey from the DB, grant additional roles, and store the ApiKey as part of the token.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.