Closed Kerrialn closed 3 years ago
Same here.
How this code:
/** @var UserResolveEvent $event */
$event = $this->eventDispatcher->dispatch(
new UserResolveEvent(
$username,
$password,
new Grant($grantType),
$client
),
OAuth2Events::USER_RESOLVE
);
$user = $event->getUser();
gets the user?
I can't found a listener for: OAuth2Events::USER_RESOLVE
@Kerrialn I have solved it like this in my project but I am surprised that there is not documented.
App\EventListener\UserResolveListener.php
<?php
namespace App\EventListener;
use App\Repository\UserRepository;
use App\Service\PasswordValidator;
use League\Bundle\OAuth2ServerBundle\Event\UserResolveEvent;
final class UserResolveListener
{
private UserRepository $userRepository; //The user repo with the method for get the user
private PasswordValidator $passwordValidator; //My custom password validator
/**
* @param UserRepository $userRepository
* @param PasswordValidator $passwordValidator
*/
public function __construct(UserRepository $userRepository, PasswordValidator $passwordValidator)
{
$this->userRepository = $userRepository;
$this->passwordValidator = $passwordValidator;
}
/**
* @param UserResolveEvent $event
*/
public function onUserResolve(UserResolveEvent $event): void
{
$user = $this->userRepository->findUserByEmail($event->getUsername());
if ($user && $this->passwordValidator->isPasswordValid($user, $event->getPassword())) {
$event->setUser($user);
}
}
}
In my services.yaml:
App\EventListener\UserResolveListener:
tags:
- { name: kernel.event_listener, event: league.oauth2_server.event.user_resolve, method: onUserResolve }
The password grant was documented in the previous repository -> https://github.com/trikoder/oauth2-bundle/blob/v3.x/docs/password-grant-handling.md
Then it was removed from this bundle as the OAuth spec deprecated it (you should use auth code grant with PKCE instead). After the removal it was brought back to ease the migration path from the Trikoder bundle (and I guess the documentation for it was forgotten to be returned in that step).
https://oauth2.thephpleague.com/authorization-server/which-grant/
Legacy Grants
The Password Grant and Implicit Grant are not included in our recommendation diagram as these grants have several drawbacks and/or are no longer considered to be best practice.
Password Grant
We strongly recommend that you use the Authorization Code flow over the Password grant for several reasons.
The Authorization Code Grant redirects to the authorization server. This provides the authorization server with the opportunity to prompt the user for multi-factor authentication options, take advantage of single-sign-on sessions, or use third-party identity providers.
The Password grant does not provide any built-in mechanism for these and must be extended with custom code.
@X-Coder264 Thank you for the response. So the password grant is no long advised, however the documentation example of the Authorization Code Grant is very limited, especially when it comes to Symfony integration. Do you know of any up-to-date guide for this?
@Kerrialn We have an old WIP PR for that on the old repository -> https://github.com/trikoder/oauth2-bundle/pull/177
It's on my TODO list to send a PR here for it (and some other documentation improvements) soon. If you or somebody else has the time to do it sooner, please do.
@X-Coder264 or anyone else who can answer. I'm happy to do it, but even with the PR you sent, the process isn't very clear.
What I did so far:
I created a new client bin/console league:oauth2-server:create-client web --grant-type "authorization_code"
I configured the config/services.php
(I use php config, normally yaml) however, I couldn't figure what is the league/oauth2-server-bundle
equivalent for the event tag trikoder.oauth2.authorization_request_resolve
I created the AuthorizationCodeListener
:
<?php
declare(strict_types=1);
namespace App\EventListener;
use League\Bundle\OAuth2ServerBundle\Event\AuthorizationRequestResolveEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
final class AuthorizationCodeListener
{
public function __construct(
private Security $security,
) {
}
public function onAuthorizationRequestResolve(AuthorizationRequestResolveEvent $event)
{
$user = $this->security->getUser();
if ($user instanceof UserInterface) {
$event->setUser($user);
$event->resolveAuthorization(true);
} else {
$response = new Response();
$response->setStatusCode(401);
$event->setResponse($response);
}
}
}
/token
endpoint with the follow parameters (which of course failed):{
"grant_type" : "authorization_code",
"client_id" : "d9d32e03....",
"client_secret" : "718fc9149......",
"code" : ??,
"redirect_uri": ??
}
basically I need to know how to implement a simple password auth using the Authorization Code flow?
If you have a simple code example of a working Symfony app with the Authorization Code flow integrated, that'd be very useful and I'll create a PR for the documentation.
@Kerrialn The authorization code grant flow has two requests.
The first request is a GET request to the authorization endpoint (example in the tests is here -> https://github.com/thephpleague/oauth2-server-bundle/blob/v0.2.0/tests/Acceptance/AuthorizationEndpointTest.php#L66).
On this request your league.oauth2_server.event.authorization_request_resolve
event listener gets triggered. For first party clients its job is to either approve the authorization request if the user is logged in (usually via the standard session login) or if not then usually a response is set on the event to redirect the user back to the login page. When the user submits the form and successfully logs in you are supposed to redirect them to the previous authorization URL (with the same query parameters that were sent previously) and then your listener gets triggered again, it sees that the user is logged in and approves the request (which results in the user getting a redirect response back to the specified redirect_uri
with the authorization code in the code
query parameter). The listener example for this "simple" case is in the documentation that was previously linked.
For third party clients there's usually an additional step (the approve/deny request page) to which the user gets redirected after they login (this logic is again handled by that listener which gets triggered on the authorization request) -> an example of such a listener can be found here -> https://gist.github.com/ajgarlag/1f84d29ee0e1a92c8878f44a902338cd.
The second request is a POST request to the token endpoint to get the access token (here you send the previously parsed authorization code from the code
query parameter). An example of this request can be found here -> https://github.com/thephpleague/oauth2-server-bundle/blob/v0.2.0/tests/Acceptance/TokenEndpointTest.php#L186
Also for PKCE you need to create a public client (it means the client has no secret on it, hence you don't send it in any request). For further auth code grant / PKCE stuff (for example how is the code verifier and challenge supposed to be generated etc.) you can also read the Laravel Passport documentation -> https://laravel.com/docs/8.x/passport#code-grant-pkce
@X-Coder264 Thanks for the detailed response, I now understand the flow.
What isn't clear is how it's integrated into Symfony, what is handled by the package and what needs to be manually coded. I've created an example project . feel free to point out what's missing or incorrect.
Authorization Code Grant with PKCE:
@Kerrialn In the auth code grant your backend app is the authorization server which means it must have a login mechanism (and the usual one is a simple session login form, or Google/Facebook/whatever login, or both). It doesn't matter if your client application is a web or mobile app that uses APIs, those are unrelated things.
The else
part in your listener (in the example project you gave) does not make sense.
else {
$response = new JsonResponse('authentication failed', 200);
$event->setResponse($response);
}
This should be changed to return a 302 redirect response to your login route which would display the login form. The redirect response should have a returnURL
query parameter to which your login flow would return after successfully logging in. The alternative is using the \Symfony\Component\Security\Http\Util\TargetPathTrait
trait for saving (and for fetching later) the URL.
The login flow is standard Symfony stuff (https://symfony.com/doc/current/security.html#form-login) so it's not separately documented here. The idea is that you either use form_login
or write your custom authenticator (https://symfony.com/doc/current/security/custom_authenticator.html) which would in its onAuthenticationSuccess
method redirect back to the returnURL
from the previous query parameter (which would have to be submitted as a hidden field in the form) or to the target path (if the target path trait was used).
The two requests described in my previous post can be sent via Postman. The first one can be sent via the browser feature of Postman (AFAIK) so that Postman opens your login form, you fill it and submit it and get a redirect response back after which you can manually copy/paste the given auth code from the query into the second request to get the access token.
Third party client example (Google, Facebook, etc Authentication)
Just to clarify, having Google or Facebook login ability on your authorization server does not have anything to do with whether the client is third party or not. A third party client would be when Google would give their users a "login with FooApplication" button (where FooApplication
is your application) - in that case from the perspective of your authorization server the Google client which initiates the auth code flow would be a third party client.
TLDR;
This package handles the auth code grant flow and the only thing it needs from a developer is the authorization listener in order to work. This package only expects that the listener does what it's expected to do (approve valid authorization requests). It does not care how you implement the login functionality in order to be able to approve those requests.
@chalasr This issue can be closed as there's nothing to do here. The auth code grant documentation issue is being tracked here -> https://github.com/thephpleague/oauth2-server-bundle/issues/51
Stack:
installed the package and added to bundle config.
config/routes.yaml
openssl genrsa -out ./var/oauth/private.key
openssl rsa -in ./var/oauth/private.key -pubout -out ./var/oauth/public.key
bin/console league:oauth2-server:create-client FrontEnd --grant-type=password --grant-type=refresh_token
/config/league_oauth2_server.php
/config/packages/security.php
Post request to
/token
Data in body:Response:
client in the database all other oauth2 tables are empty: