Skip to content

Conversation

@SimonVanacco
Copy link

@SimonVanacco SimonVanacco commented Mar 25, 2025

This PR adds support for device code grants, which has been available for some time in oauth2-server

Ready to be tested, I'd love to get some feedback if you think some areas could be improved !

Design considerations

  • I've decided to let end-users handle the device code verification page on their own. This is to prevent having to require twig in the bundle itself and create more complexity to accommodate all use cases. An example controller has been added in the documentation
  • This does not support verification_uri_complete, only verification_uri
  • Logic for code approval is in DeviceCodeRepository::approveDeviceCode. I was unable to re-use the logic from oauth2-server as they fetch the code by "device_code" instead of by "user_code". Not sure if this is a mistake in the parent bundle or some use-case I don't understand, but it does not accommodate most people use-cases. I'm open to moving the logic somewhere else if you find a better place for it !
  • I've added some basic tests but this is not my strong suit : please let me know if I missed something :)

Copy link
Member

@chalasr chalasr 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! Can you rebase it? Here are some comments also

SimonVanacco added a commit to SimonVanacco/oauth2-server-bundle that referenced this pull request Apr 26, 2025
SimonVanacco added a commit to SimonVanacco/oauth2-server-bundle that referenced this pull request Apr 26, 2025
@SimonVanacco
Copy link
Author

Hi @chalasr

I'm not entirely sure I understood correctly what you wanted for the docs/device-code-grant.md improvements, but I tried my best to fit to your feedback :)

PR is rebased !

@Mika56
Copy link

Mika56 commented Oct 20, 2025

Interested in this PR, but there's been no activity for a few months. Anything we can do to help?

@Renrhaf
Copy link

Renrhaf commented Oct 21, 2025

+1 interested as well !

Copy link
Contributor

@ajgarlag ajgarlag left a comment

Choose a reason for hiding this comment

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

Thank you for your work here. I've managed to create a working demo. I've reviewed your code and added some suggestions.

$this->deviceCodeManager->save($deviceCode, $newDeviceCode);
}

public function approveDeviceCode(string $userCode, string $userId): void
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure whether this method should be included in the bundle. I've created a working demo that doesn't use this it.

Either way, I'll move this method to the DeviceCodeManagerInterface and rename it resolveDeviceCode, adding a third required boolean parameter to indicate whether the request has been approved.

Copy link
Author

@SimonVanacco SimonVanacco Dec 2, 2025

Choose a reason for hiding this comment

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

I'm not sure whether this method should be included in the bundle

Oauth2-server does bring a DeviceCodeGrant::completeDeviceAuthorizationRequest method already but I'm struggling to understand how they expect it to be used. It fetches from the manager by $deviceCode where I'm pretty sure we need to use $userCode... Maybe I'm missing something.

I'll move this method to the DeviceCodeManagerInterface and rename it resolveDeviceCode, adding a third required boolean parameter to indicate whether the request has been approved.

I'm happy to move to DeviceCodeManagerInterface, however it will result in two duplicated methods with the exact same logic (for in memory and doctrine) - If that's fine by you let me know and I'll do another commit.

Copy link
Contributor

Choose a reason for hiding this comment

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

Having reviewed it again, this method should be completely removed. There is a method in the league/oauth2-server code to approve (or deny) a device code authorization request: AuthorizationServer::completeDeviceAuthorizationRequest.

@chalasr
Copy link
Member

chalasr commented Nov 17, 2025

Happy to merge once Antonio's review comments are resolved.

@ajgarlag
Copy link
Contributor

ajgarlag commented Dec 2, 2025

@SimonVanacco Friendly ping

@SimonVanacco
Copy link
Author

Hi @ajgarlag

Thank you for the review, and sorry for the delay. The past few weeks have been a bit hectic :)

I implemented some of your requests (new configuration options, refactoring of the repository) and left comments on the ones I won't be able to do myself without some guidance : Supporting Symfony routes for the verification_uri and removing approveDeviceCode.

Let me know your thoughts on the update and I'll try to be quicker to respond now !

@chalasr
Copy link
Member

chalasr commented Dec 2, 2025

Thanks. Supporting route names can be done in a follow-up PR, no problem. Regarding approveDeviceCode, I'll let Antonio have the final word as I have no strong opinion.

Comment on lines +175 to +188
private function updateDeviceCodeModel(
DeviceCodeInterface $deviceCode,
DeviceCodeEntityInterface $deviceCodeEntity,
): DeviceCodeInterface {
if ($deviceCodeEntity->getLastPolledAt()) {
$deviceCode->setLastPolledAt($deviceCodeEntity->getLastPolledAt());
}

if ($deviceCodeEntity->getUserIdentifier()) {
$deviceCode->setUserIdentifier($deviceCodeEntity->getUserIdentifier());
$deviceCode->setUserApproved($deviceCodeEntity->getUserApproved());
}

return $deviceCode;
Copy link
Contributor

Choose a reason for hiding this comment

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

As this method mutates the received $deviceCode, I think it should not return anything.

Suggested change
private function updateDeviceCodeModel(
DeviceCodeInterface $deviceCode,
DeviceCodeEntityInterface $deviceCodeEntity,
): DeviceCodeInterface {
if ($deviceCodeEntity->getLastPolledAt()) {
$deviceCode->setLastPolledAt($deviceCodeEntity->getLastPolledAt());
}
if ($deviceCodeEntity->getUserIdentifier()) {
$deviceCode->setUserIdentifier($deviceCodeEntity->getUserIdentifier());
$deviceCode->setUserApproved($deviceCodeEntity->getUserApproved());
}
return $deviceCode;
private function updateDeviceCodeModel(
DeviceCodeInterface $deviceCode,
DeviceCodeEntityInterface $deviceCodeEntity,
): void {
if ($deviceCodeEntity->getLastPolledAt()) {
$deviceCode->setLastPolledAt($deviceCodeEntity->getLastPolledAt());
}
if ($deviceCodeEntity->getUserIdentifier()) {
$deviceCode->setUserIdentifier($deviceCodeEntity->getUserIdentifier());
$deviceCode->setUserApproved($deviceCodeEntity->getUserApproved());
}

Comment on lines +133 to +136
$client = $this->clientRepository->getClientEntity($deviceCode->getClient()->getIdentifier());
if ($client) {
$deviceCodeEntity->setClient($client);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Prevent the casting of an object to a boolean value

Suggested change
$client = $this->clientRepository->getClientEntity($deviceCode->getClient()->getIdentifier());
if ($client) {
$deviceCodeEntity->setClient($client);
}
if (null !== $client = $this->clientRepository->getClientEntity($deviceCode->getClient()->getIdentifier())) {
$deviceCodeEntity->setClient($client);
}

Comment on lines +144 to +146
if ($deviceCode->getLastPolledAt()) {
$deviceCodeEntity->setLastPolledAt($deviceCode->getLastPolledAt());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Prevent the casting of an object to a boolean value, and prevent calling the same method twice

Suggested change
if ($deviceCode->getLastPolledAt()) {
$deviceCodeEntity->setLastPolledAt($deviceCode->getLastPolledAt());
}
if (null !== $lastPolledAt = $deviceCode->getLastPolledAt()) {
$deviceCodeEntity->setLastPolledAt($lastPolledAt);
}

Comment on lines +137 to +139
if ($deviceCode->getUserIdentifier()) {
$deviceCodeEntity->setUserIdentifier($deviceCode->getUserIdentifier());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Prevent the casting of a string to a boolean value, and prevent calling the same method twice

Suggested change
if ($deviceCode->getUserIdentifier()) {
$deviceCodeEntity->setUserIdentifier($deviceCode->getUserIdentifier());
}
if (null !== $userIdentifier = $deviceCode->getUserIdentifier()) {
$deviceCodeEntity->setUserIdentifier($userIdentifier);
}

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.

5 participants