if (!$this->checkAuthentication()) {
  throw new InvalidCredentialsException('Usuário inválido.');
}
if (!$this->checkAuthorization()) {
  throw new UnauthorizedException('Você não possui permissão.');
}
return new MyResponse([]);
 
          Fonte: dracony.org
Psr\Http\Message\ServerRequestInterfacePsr\Http\Message\ResponseInterfacePsr\Http\Message\MessageInterfacePsr\Http\Message\RequestInterfacePsr\Http\Message\StreamInterfacePsr\Http\Message\UriInterfacePsr\Http\Message\UploadedFileInterfaceuse Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Interop\Http\ServerMiddleware\DelegateInterface as Delegate;
class MyMiddleware implements MiddlewareInterface {
  public function process(Request $request, Delegate $delegate) {
    // Processa a requisição e retorna a resposta
    // Ou chama o próximo middleware capaz de responder
    return $delegate->process($request);
  }
}Fonte: PHP League OAuth 2.0 Server
audsubjtiissiatnbfexp
              eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxMjM0NTYiLCJhdWQiOiJQSFAgQ29uZmVyZW5jZSAyMDE3Iiwic3ViIjoiRXhlbXBsbyBKV1QiLCJ1c2VyIjp7ImlkIjoxLCJuYW1lIjoidmNhbXBpdGVsbGkifSwic2NvcGVzIjpbInNwZWFrZXIiXX0.8OZ6wkqtqfG9YjI5OGNN4Isyr5kdeZzhYIqHCC9u4cA
            
            {
  "alg": "HS256",
  "typ": "JWT"
}{
  "jti": "123456",
  "aud": "PHP Conference 2017",
  "sub": "Exemplo JWT",
  "user": {
  "id": 1,
  "name": "vcampitelli"
  },
  "scopes": [
  "speaker"
  ]
}HMACSHA256(
  base64Url(header) + "." +  
  base64Url(payload),
  "kJf;N%Z$K1oQ>dPN|LB?Vno$"
)composer create-project zendframework/zend-expressive-skeleton
composer require league/oauth2-server 
        ConfigProviderResourceServerMiddlewareTokenActionAuthorizationServerAccessTokenEntityAccessTokenRepositoryClientEntityClientRepositoryScopeEntityScopeRepositorypublic function getRoutes() {
  return [
    [
      'name'            => 'oauth.token',
      'path'            => '/token',
      'middleware'      => TokenAction::class,
      'allowed_methods' => ['POST']
    ]
  ];
}public function getDependencies() {
  return [
    'factories'  => [
      TokenAction::class              => TokenActionFactory::class,
      ClientRepository::class         => RepositoryFactory::class,
      ScopeRepository::class          => RepositoryFactory::class,
      AccessTokenRepository::class    => RepositoryFactory::class,
      ResourceServerMiddleware::class => ResourceServerFactory::class,
      AuthorizationServer::class      => AuthorizationServerFactory::class
    ]
  ];
}public function process(
  ServerRequestInterface $request,
  DelegateInterface $delegate
) {
  $request = $this->resourceServer->validateAuthenticatedRequest($request);
  $clientId = $request->getAttribute('oauth_client_id');
  if ((empty($clientId)) || (!$this->isClientAuthorized($clientId, $request))) {
    throw OAuthServerException::accessDenied('Unauthorized client');
  }
  return $delegate->process($request);
}public function process(
  ServerRequestInterface $request,
  DelegateInterface $delegate
) {
  return $this->authorizationServer->handleTokenRequest($request);
}public function handleTokenRequest(ServerRequestInterface $request) {
  try {
    $response = new \Zend\Diactoros\Response\JsonResponse([]);
    return $this->authorizationServer
      ->respondToAccessTokenRequest($request, $response);
  } catch (\League\OAuth2\Server\Exception\OAuthServerException $e) {
    return $e->generateHttpResponse($response);
  }
}public function __invoke(ContainerInterface $container) {
  $config = $container->get('config')['oauth'];
  $server = new \League\OAuth2\Server\AuthorizationServer(
    $container->get(ClientRepository::class),
    $container->get(AccessTokenRepository::class),
    $container->get(ScopeRepository::class),
    $config['private_key'],
    $config['encryption_key']
  );
  $server->enableGrantType(
    new \League\OAuth2\Server\Grant\ClientCredentialsGrant(),
    new \DateInterval('PT1H') // 1 hora para expiração
  );
  return $server;
}public function finalizeScopes(
  array $scopes,
  $grantType,
  ClientEntityInterface $clientEntity,
  $userIdentifier = null
) {
  // Valida os scopes que o cliente está pedindo,
  // adicionando novos e removendo os inválidos
  return $scopes;
}public function convertToJWT(\League\OAuth2\Server\CryptKey $key) {
  return (new \Lcobucci\JWT\Builder())
    ->setAudience('PHP Conference 2017')
    ->setSubject('OAuth 2 com JWT')
    ->set('scopes', $this->getScopes())
    ->setIssuedAt(time())
    ->setExpiration($this->getExpiryDateTime())
    ->sign(
      new \Lcobucci\JWT\Signer\Rsa\Sha256(),
      (new \Lcobucci\JWT\Signer\Key())->getPrivateKey(
        $key->getKeyPath(),
        $key->getPassphrase()
      )
    )->getToken();
}curl -X POST -d client_id=api -d client_secret=mysecret \
 -d scope="user_list user_create" https://api.local/token{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjA1ODI3NTJkOTNiMTI4ZTYifQ.eyJhdWQiOiIxIiwic3ViIjoiIiwianRpIjoiMDU4Mjc1MmQ5M2IxMjhlNiIsInNjb3BlcyI6W10sImlhdCI6MTUwNzQwNTI0NCwiZXhwIjoxNTA3NDA4ODQ0fQ.P7V0TBzBOiTXqUk48wMFMhEPYjvT3EXOMqztRHXGqAmZJm7Uhd7jRejwwE-YBPu4lOcRhDsxoYbM5b_VVc1BRgGf824WpWdW1Mg5FALHTlGJqLvVmHYbZqPahBNei4_BXJmtZ7e7Vp9IkjY3qTR4W9h4BjieI7P0TLTIS0S3Q1c"
}curl -X GET \
 -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjA1ODI3NTJkOTNiMTI4ZTYifQ.eyJhdWQiOiIxIiwic3ViIjoiIiwianRpIjoiMDU4Mjc1MmQ5M2IxMjhlNiIsInNjb3BlcyI6W10sImlhdCI6MTUwNzQwNTI0NCwiZXhwIjoxNTA3NDA4ODQ0fQ.P7V0TBzBOiTXqUk48wMFMhEPYjvT3EXOMqztRHXGqAmZJm7Uhd7jRejwwE-YBPu4lOcRhDsxoYbM5b_VVc1BRgGf824WpWdW1Mg5FALHTlGJqLvVmHYbZqPahBNei4_BXJmtZ7e7Vp9IkjY3qTR4W9h4BjieI7P0TLTIS0S3Q1c" \
 https://api.local/users{ ... }Zend Expressive com OAuth 2 e JWT