This repository was archived by the owner on Jan 29, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 20
RFC: Separate authorization and token endpoints #42
Merged
ezimuel
merged 13 commits into
zendframework:master
from
tux-rampage:feature/oauth2-rfc-compliance
Sep 21, 2018
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
ec97704
Separate authorization and token endpoints
tux-rampage 4178d45
Update test cases
tux-rampage f71faf4
Remove trait
tux-rampage bac7c6a
Remove HTTP method check
tux-rampage 2a74fa5
Add missing service config
tux-rampage 755b8aa
Fix coding style
tux-rampage 4547811
Add auth handler test (wip)
tux-rampage 9be2919
Add missing authorization handler tests
tux-rampage 28497b2
Add token endpoint handler tests
tux-rampage b9f3775
fix coding style
tux-rampage 885d462
Update docs
tux-rampage e315586
Remove unused imports
tux-rampage cee014a
Add changelog entries
tux-rampage File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# Implement an authorization server | ||
|
||
This library provides the basics to implement an authorization server | ||
for your application. | ||
|
||
Since there are authorization flows that require user interaction, | ||
your application is expected to provide the middleware to handle this. | ||
|
||
## Add the token endpoint | ||
|
||
This is the most simple part, since this library provides | ||
`Zend\Expressive\Authentication\OAuth2\TokenHandler` to deal with it. | ||
|
||
This endpoint must accept POST requests. | ||
|
||
For example: | ||
|
||
```php | ||
use Zend\Expressive\Authentication\OAuth2; | ||
|
||
$app->route('/oauth2/token', OAuth2\TokenHandler::class, ['POST']); | ||
``` | ||
|
||
## Add the authorization endpoint | ||
|
||
The authorization endpoint is an url of to which the client redirects | ||
to obtain an access token or authorization code. | ||
|
||
This endpoint must accept GET requests and should: | ||
|
||
- Validate the request (especially for a valid client id and redirect url) | ||
- Make sure the User is authenticated (for example by showing a login | ||
prompt if needed) | ||
- Optionally request the users consent to grant access to the client | ||
- Redirect to a specified url of the client with success or error information | ||
|
||
The first and the last part is provided by this library. | ||
|
||
For example, to add the authorization endpoint you can declare a middleware pipe | ||
to compose these parts: | ||
|
||
```php | ||
use Zend\Expressive\Authentication\OAuth2; | ||
|
||
$app->route('/oauth2/authorize', [ | ||
OAuth2\AuthorizatonMiddleware, | ||
|
||
// The followig middleware is provided by your application (see below) | ||
Application\OAuthAuthorizationMiddleware::class, | ||
|
||
OAuth2\AuthorizationHandler | ||
], ['GET', 'POST']); | ||
``` | ||
|
||
In your `Application\OAuthAuthorizationMiddleware`, you'll have access | ||
to the `League\OAuth2\Server\RequestTypes\AuthorizationRequest` via the | ||
psr-7 request. Your middleware should populate the user entity with `setUser()` and the | ||
user's consent decision with `setAuthorizationApproved()` to this authorization | ||
request instance. | ||
|
||
```php | ||
<?php | ||
|
||
namespace Application; | ||
|
||
use Psr\Http\Server\MiddlewareInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Server\RequestHandlerInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest; | ||
|
||
class OAuthAuthorizationMiddleware implements MiddlewareInterface | ||
{ | ||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface | ||
{ | ||
// Assume a middleware handled the authentication check and | ||
// populates the user object which also implements the | ||
// OAuth2 UserEntityInterface | ||
$user = $request->getAttribute('authenticated_user'); | ||
|
||
// Assume some middleware handles and populates a session | ||
// container | ||
$session = $request->getAttribute('session'); | ||
|
||
// This is populated by the previous middleware | ||
/** @var AuthorizationRequest $authRequest */ | ||
$authRequest = $request->getAttribute(AuthorizationRequest::class); | ||
|
||
// the user is authenticated | ||
if ($user) { | ||
$authRequest->setUser($user); | ||
|
||
// Assume all clients are trusted, but you could | ||
// handle consent here or within the next middleware | ||
// as needed | ||
$authRequest->setAuthorizationApproved(true); | ||
|
||
return $handler->handle($request); | ||
} | ||
|
||
// The user is not authenticated, show login form ... | ||
|
||
// Store the auth request state | ||
// NOTE: Do not attempt to serialize or store the authorization | ||
// request object. Store the query parameters instead and redirect | ||
// with these to this endpoint again to replay the request. | ||
$session['oauth2_request_params'] = $request->getQueryParams(); | ||
|
||
return new RedirectResponse('/oauth2/login'); | ||
} | ||
} | ||
``` | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-expressive-authentication-oauth2 for the canonical source repository | ||
* @copyright Copyright (c) 2017-2018 Zend Technologies USA Inc. (https://www.zend.com) | ||
* @license https://github.com/zendframework/zend-expressive-authentication-oauth2/blob/master/LICENSE.md | ||
* New BSD License | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Zend\Expressive\Authentication\OAuth2; | ||
|
||
use League\OAuth2\Server\AuthorizationServer; | ||
use phpDocumentor\Reflection\Types\This; | ||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Server\RequestHandlerInterface; | ||
|
||
/** | ||
* Handles the already validated and competed authorization request | ||
* | ||
* This will perform the required redirect to the requesting party. | ||
* The request must provide an attribute `League\OAuth2\Server\AuthorizationServer` | ||
* that contains the validated OAuth2 request | ||
* | ||
* @see https://tools.ietf.org/html/rfc6749#section-3.1.1 | ||
*/ | ||
class AuthorizationHandler implements RequestHandlerInterface | ||
{ | ||
/** | ||
* @var AuthorizationServer | ||
*/ | ||
private $server; | ||
|
||
/** | ||
* @var callable | ||
*/ | ||
private $responseFactory; | ||
|
||
public function __construct(AuthorizationServer $server, callable $responseFactory) | ||
{ | ||
$this->server = $server; | ||
$this->responseFactory = function () use ($responseFactory): ResponseInterface { | ||
return $responseFactory(); | ||
}; | ||
} | ||
|
||
public function handle(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
$authRequest = $request->getAttribute(AuthorizationRequest::class); | ||
ezimuel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return $this->server->completeAuthorizationRequest($authRequest, ($this->responseFactory)()); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-expressive-authentication-oauth2 for the canonical source repository | ||
* @copyright Copyright (c) 2017-2018 Zend Technologies USA Inc. (https://www.zend.com) | ||
* @license https://github.com/zendframework/zend-expressive-authentication-oauth2/blob/master/LICENSE.md | ||
* New BSD License | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Zend\Expressive\Authentication\OAuth2; | ||
|
||
use League\OAuth2\Server\AuthorizationServer; | ||
use Psr\Container\ContainerInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
final class AuthorizationHandlerFactory | ||
{ | ||
public function __invoke(ContainerInterface $container) | ||
{ | ||
return new AuthorizationHandler( | ||
$container->get(AuthorizationServer::class), | ||
$container->get(ResponseInterface::class) | ||
); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
/** | ||
* @see https://github.com/zendframework/zend-expressive-authentication-oauth2 for the canonical source repository | ||
* @copyright Copyright (c) 2017-2018 Zend Technologies USA Inc. (https://www.zend.com) | ||
* @license https://github.com/zendframework/zend-expressive-authentication-oauth2/blob/master/LICENSE.md | ||
* New BSD License | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Zend\Expressive\Authentication\OAuth2; | ||
|
||
use League\OAuth2\Server\AuthorizationServer; | ||
use League\OAuth2\Server\Exception\OAuthServerException; | ||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Server\MiddlewareInterface; | ||
use Psr\Http\Server\RequestHandlerInterface; | ||
|
||
/** | ||
* Implements OAuth2 authorization request validation | ||
* | ||
* Performs checks if the OAuth authorization request is valid and populates it | ||
* to the next handler via the request object as attribute with the key | ||
* `League\OAuth2\Server\AuthorizationServer` | ||
* | ||
* The next handler should take care of checking the resource owner's authentication and | ||
* consent. It may intercept to ensure authentication and consent before populating it to | ||
* the authorization request object | ||
* | ||
* @see https://oauth2.thephpleague.com/authorization-server/auth-code-grant/ | ||
* @see https://oauth2.thephpleague.com/authorization-server/implicit-grant/ | ||
*/ | ||
class AuthorizationMiddleware implements MiddlewareInterface | ||
{ | ||
/** | ||
* @var AuthorizationServer | ||
*/ | ||
protected $server; | ||
|
||
/** | ||
* @var callable | ||
*/ | ||
protected $responseFactory; | ||
|
||
public function __construct(AuthorizationServer $server, callable $responseFactory) | ||
{ | ||
$this->server = $server; | ||
$this->responseFactory = function () use ($responseFactory) : ResponseInterface { | ||
return $responseFactory(); | ||
}; | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface | ||
{ | ||
$response = ($this->responseFactory)(); | ||
|
||
try { | ||
$authRequest = $this->server->validateAuthorizationRequest($request); | ||
|
||
// The next handler must take care of providing the | ||
// authenticated user and the approval | ||
$authRequest->setAuthorizationApproved(false); | ||
ezimuel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return $handler->handle($request->withAttribute(AuthorizationRequest::class, $authRequest)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pass the the auth request via the server request object to the next handler to complete it. |
||
} catch (OAuthServerException $exception) { | ||
// The validation throws this exception if the request is not valid | ||
// for example when the client id is invalid | ||
return $exception->generateHttpResponse($response); | ||
} catch (\Exception $exception) { | ||
ezimuel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) | ||
->generateHttpResponse($response); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.