Skip to content

Commit

Permalink
Passport social grant v4 (#38)
Browse files Browse the repository at this point in the history
Supports Laravel 6 and Passport v8
  • Loading branch information
adaojunior authored Nov 13, 2019
1 parent 46e03e3 commit 8eab6c5
Show file tree
Hide file tree
Showing 19 changed files with 474 additions and 111 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ composer.lock
.DS_Store
Thumbs.db
phpunit.xml
.phpunit.result.cache
.idea
1 change: 1 addition & 0 deletions .styleci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
preset: laravel
12 changes: 8 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ cache:
- vendor

php:
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3

install:
- travis_retry composer install --no-interaction --prefer-source

script:
- vendor/bin/phpunit
- chmod 600 tests/Stubs/private.key
- composer test

branches:
only:
- master
23 changes: 17 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "adaojunior/passport-social-grant",
"description": "Social guard for Laravel Passport",
"description": "Social grant for Laravel Passport",
"type": "library",
"keywords": [
"oauth",
Expand All @@ -14,24 +14,35 @@
"authors": [
{
"name": "Adão Júnior",
"email": "itsjunnior@gmail.com",
"email": "dev@pardim.com",
"homepage": "https://github.com/adaojunior",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Adaojunior\\Passport\\": "src/"
"Adaojunior\\PassportSocialGrant\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Adaojunior\\PassportSocialGrant\\Tests\\": "tests/"
}
},
"require": {
"php": ">=5.6.4",
"laravel/passport": "^2.0|^3.0|^4.0|^5.0|^6.0|^7.0"
"php": "^7.2",
"laravel/passport": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.4"
},
"scripts": {
"test": "vendor/bin/phpunit --colors=always"
},
"extra": {
"laravel": {
"providers": [
"Adaojunior\\Passport\\SocialGrantServiceProvider"
"Adaojunior\\PassportSocialGrant\\SocialGrantServiceProvider"
]
}
}
Expand Down
3 changes: 1 addition & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Package Test Suite">
<testsuite name="Passport Social Grant Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
Expand Down
84 changes: 33 additions & 51 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,93 +1,75 @@
# Social Grant for Laravel Passport

This package is useful to combine your Oauth2 Server with Social Login (facebook, google, github ...).

If you have a api that accepts registration/login using google, facebook, github or any other social login,
you will be able to exchange the access token given by the social login provider to a `access_token` + `refresh_token` from our own application.
You will be able to resolve a existing user or create a new user if a user is not yet registered on your application.
This package adds a social grant to your Oauth2 Server.

## Installation

This package can be installed through Composer.
You can install the package via composer:

```
composer require adaojunior/passport-social-grant
```

In Laravel 5.5 the service provider will automatically get registered. In older versions of the framework just add the service provider in config/app.php file:

The package will automatically register its service provider. Or you may manually add the service provider in your `config/app.php` file:

```php
// config/app.php
'providers' => [
...
Adaojunior\Passport\SocialGrantServiceProvider::class,
...
// ...
Adaojunior\PassportSocialGrant\SocialGrantServiceProvider::class,
];
```

You must also implement `Adaojunior\Passport\SocialUserResolverInterface`:
## Setup

1. Implement the `SocialGrantUserProvider` interface:

```php
...
<?php

use Adaojunior\Passport\SocialGrantException;
use Adaojunior\Passport\SocialUserResolverInterface;
namespace App\SocialGrant;

class SocialUserResolver implements SocialUserResolverInterface
{
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Contracts\Auth\Authenticatable;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use Adaojunior\PassportSocialGrant\SocialGrantUserProvider;

class UserProvider implements SocialGrantUserProvider
{
/**
* Resolves user by given network and access token.
*
* @param string $network
* @param string $accessToken
* @return \Illuminate\Contracts\Auth\Authenticatable
*/
public function resolve($network, $accessToken, $accessTokenSecret = null)
{
switch ($network) {
case 'facebook':
return $this->authWithFacebook($accessToken);
break;
default:
throw SocialGrantException::invalidNetwork();
break;
}
}


/**
* Resolves user by facebook access token.
* Retrieve a user by provider and access token.
*
* @param string $provider
* @param string $accessToken
* @return \App\User
* @param ClientEntityInterface $client
* @return Authenticatable|null
*/
protected function authWithFacebook($accessToken)
public function getUserByAccessToken(string $provider, string $accessToken, ClientEntityInterface $client):? Authenticatable
{
...

}
}

```

Register on AppServiceProvider:
2. Bind `SocialGrantUserProvider` interface to your implementation in the `register` method of your application service provider `app/Providers/AppServiceProvider.php`:

```php
$this->app->singleton(SocialUserResolverInterface::class, SocialUserResolver::class);
$this->app->bind(
Adaojunior\PassportSocialGrant\SocialGrantUserProvider::class,
App\SocialGrant\UserProvider::class
);
```

## Usage

## Request

```php
$response = $http->post('http://your-app.com/oauth/token', [
$response = $http->post('http://your.app/oauth/token', [
'form_params' => [
'grant_type' => 'social',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'network' => 'facebook', /// or any other network that your server is able to resolve.
'access_token' => 'A_ACCESS_TOKEN_PROVIDED_BY_THE_SOCIAL_LOGIN_PROVIDER',
'client_id' => $clientId,
'client_secret' => $clientSecret,
'provider' => $providerName, // name of provider (e.g., 'facebook', 'google' etc.)
'access_token' => $providerAccessToken, // access token issued by specified provider
],
]);
```
68 changes: 43 additions & 25 deletions src/SocialGrant.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?php

namespace Adaojunior\Passport;
namespace Adaojunior\PassportSocialGrant;

use DateInterval;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Passport\Bridge\User as UserEntity;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AbstractGrant;
Expand All @@ -14,77 +16,93 @@

class SocialGrant extends AbstractGrant
{
/** @var SocialUserResolverInterface */
protected $resolver;
private $provider;

public function __construct(
SocialUserResolverInterface $resolver,
RefreshTokenRepositoryInterface $refreshTokenRepository
) {
$this->resolver = $resolver;
public function __construct(SocialGrantUserProvider $provider, RefreshTokenRepositoryInterface $refreshTokenRepository)
{
$this->provider = $provider;
$this->setRefreshTokenRepository($refreshTokenRepository);
$this->refreshTokenTTL = new \DateInterval('P1M');
$this->refreshTokenTTL = new DateInterval('P1M');
}

public function getIdentifier()
{
return 'social';
}

/**
* @param ServerRequestInterface $request
* @param ResponseTypeInterface $responseType
* @param DateInterval $accessTokenTTL
* @return ResponseTypeInterface
* @throws OAuthServerException
* @throws \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException
*/
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
DateInterval $accessTokenTTL
) {

// Validate request
$client = $this->validateClient($request);

$scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request), $this->defaultScope);

$user = $this->validateUser($request);
$user = $this->validateUser($request, $client);

// Finalize the requested scopes
$scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
$finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());

// Issue and persist new tokens
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $scopes);
// Issue and persist new access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$responseType->setAccessToken($accessToken);

// Issue and persist new refresh token if given
$refreshToken = $this->issueRefreshToken($accessToken);

// Inject tokens into response
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
$responseType->setRefreshToken($refreshToken);
}

return $responseType;
}

/**
* @param ServerRequestInterface $request
* @param ClientEntityInterface $client
* @return UserEntityInterface
* @throws OAuthServerException
*/
protected function validateUser(ServerRequestInterface $request)
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
{
$user = $this->resolver->resolve(
$this->getParameter('network', $request),
$user = $this->provider->getUserByAccessToken(
$this->getParameter('provider', $request),
$this->getParameter('access_token', $request),
$this->getParameter('access_token_secret', $request, false)
$client
);

if($user instanceof Authenticatable)
{
if ($user instanceof Authenticatable) {
$user = new UserEntity($user->getAuthIdentifier());
}

if ($user instanceof UserEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidCredentials();
throw OAuthServerException::invalidGrant();
}

return $user;
}

/**
* @param $param
* @param ServerRequestInterface $request
* @param bool $required
* @return string|null
* @throws OAuthServerException
*/
protected function getParameter($param, ServerRequestInterface $request, $required = true)
{
$value = $this->getRequestParameter($param, $request);
Expand Down
8 changes: 4 additions & 4 deletions src/SocialGrantException.php
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<?php

namespace Adaojunior\Passport;
namespace Adaojunior\PassportSocialGrant;

use League\OAuth2\Server\Exception\OAuthServerException;

class SocialGrantException extends OAuthServerException
{
public static function invalidNetwork()
public static function invalidProvider()
{
return self::invalidRequest('network', "Invalid network");
return self::invalidRequest('provider', 'Invalid provider');
}

public static function invalidAccessToken()
{
return self::invalidRequest('access_token', "Invalid access token");
return self::invalidRequest('access_token', 'Invalid access token');
}
}
6 changes: 3 additions & 3 deletions src/SocialGrantServiceProvider.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Adaojunior\Passport;
namespace Adaojunior\PassportSocialGrant;

use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Bridge\RefreshTokenRepository;
Expand All @@ -11,7 +11,7 @@ class SocialGrantServiceProvider extends ServiceProvider
{
public function register()
{
app()->afterResolving(AuthorizationServer::class, function(AuthorizationServer $server) {
$this->app->resolving(AuthorizationServer::class, function (AuthorizationServer $server) {
$grant = $this->makeGrant();
$server->enableGrantType($grant, Passport::tokensExpireIn());
});
Expand All @@ -20,7 +20,7 @@ public function register()
protected function makeGrant()
{
$grant = new SocialGrant(
$this->app->make(SocialUserResolverInterface::class),
$this->app->make(SocialGrantUserProvider::class),
$this->app->make(RefreshTokenRepository::class)
);

Expand Down
Loading

0 comments on commit 8eab6c5

Please sign in to comment.