Skip to content

Commit b6a7c4a

Browse files
authored
Merge pull request #6 from designmynight/introspect-guard
Introspect guard
2 parents b221626 + 2ed4dc8 commit b6a7c4a

File tree

7 files changed

+312
-122
lines changed

7 files changed

+312
-122
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "designmynight/laravel-oauth-introspect-middleware",
3-
"description": "Laravel Middleware for letting a resource owner verify a OAuth2 access tokens with a remote authorization server",
2+
"name": "designmynight/laravel-oauth-introspection-client",
3+
"description": "Laravel package for letting a resource owner verify OAuth2 access tokens with a remote authorization server",
44
"type": "library",
55
"license": "MIT",
66
"authors": [

src/Facades/Introspect.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace DesignMyNight\Laravel\OAuth2\Facades;
4+
5+
use DesignMyNight\Laravel\OAuth2\Introspect as IntrospectService;
6+
use Illuminate\Support\Facades\Facade;
7+
8+
class Introspect extends Facade
9+
{
10+
protected static function getFacadeAccessor()
11+
{
12+
return IntrospectService::class;
13+
}
14+
}

src/Guard/IntrospectGuard.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace DesignMyNight\Laravel\OAuth2\Guard;
4+
5+
use DesignMyNight\Laravel\OAuth2\Introspect;
6+
use Illuminate\Contracts\Auth\Authenticatable;
7+
use Illuminate\Contracts\Auth\Guard;
8+
9+
class IntrospectGuard implements Guard
10+
{
11+
protected $user;
12+
13+
public function __construct(Introspect $introspect)
14+
{
15+
$this->introspect = $introspect;
16+
}
17+
18+
public function authenticate()
19+
{
20+
return $this->check();
21+
}
22+
23+
public function check()
24+
{
25+
return ! is_null($this->user());
26+
}
27+
28+
public function guest()
29+
{
30+
return !$this->check();
31+
}
32+
33+
public function id()
34+
{
35+
return $this->check() ? $this->user()->getKey() : null;
36+
}
37+
38+
public function user()
39+
{
40+
if ($this->user === null) {
41+
$this->user = $this->introspect
42+
->verifyToken()
43+
->getUser();
44+
}
45+
46+
return $this->user;
47+
}
48+
49+
public function setUser(Authenticatable $user): void
50+
{
51+
$this->user = $user;
52+
}
53+
54+
public function validate(array $credentials = []): bool
55+
{
56+
return true;
57+
}
58+
}

src/Introspect.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
namespace DesignMyNight\Laravel\OAuth2;
4+
5+
use GuzzleHttp\Exception\RequestException;
6+
use Illuminate\Contracts\Auth\Authenticatable;
7+
use Illuminate\Auth\AuthenticationException;
8+
use Illuminate\Foundation\Auth\User;
9+
use Illuminate\Http\Request;
10+
use Laravel\Passport\Exceptions\MissingScopeException;
11+
12+
class Introspect
13+
{
14+
protected $accessTokenCacheKey = 'access_token';
15+
protected $client = null;
16+
protected $result;
17+
protected $userDataKey = 'user';
18+
protected $userModelClass = User::class;
19+
20+
public function __construct(IntrospectClient $client, Request $request)
21+
{
22+
$this->client = $client;
23+
$this->request = $request;
24+
}
25+
26+
protected function getIntrospectionResult()
27+
{
28+
if ($this->result !== null) {
29+
return $this->result;
30+
}
31+
32+
try {
33+
$this->result = $this->client->introspect($this->request->bearerToken());
34+
} catch (RequestException $e) {
35+
if ($e->hasResponse()) {
36+
$result = json_decode((string) $e->getResponse()->getBody(), true);
37+
38+
if (isset($result['error'])) {
39+
throw new AuthenticationException($result['error']['title'] ?? '');
40+
}
41+
}
42+
43+
throw new AuthenticationException($e->getMessage());
44+
}
45+
46+
return $this->result;
47+
}
48+
49+
public function mustHaveScopes(array $requiredScopes = [])
50+
{
51+
$result = $this->getIntrospectionResult();
52+
$givenScopes = explode(' ', $result['scope']);
53+
$missingScopes = array_diff($requiredScopes, $givenScopes);
54+
55+
if (count($missingScopes) > 0) {
56+
throw new MissingScopeException($missingScopes);
57+
}
58+
}
59+
60+
public function setUserDataKey(string $key): self
61+
{
62+
$this->userDataKey = $key;
63+
64+
return $this;
65+
}
66+
67+
public function setUserModelClass(string $class): self
68+
{
69+
$this->userModelClass = $class;
70+
71+
return $this;
72+
}
73+
74+
public function tokenIsActive(): bool
75+
{
76+
$result = $this->getIntrospectionResult();
77+
78+
return $result['active'] === true;
79+
}
80+
81+
public function tokenIsNotActive(): bool
82+
{
83+
return !$this->tokenIsActive();
84+
}
85+
86+
public function getUser()
87+
{
88+
$result = $this->getIntrospectionResult();
89+
90+
if (isset($result[$this->userDataKey]) && !empty($result[$this->userDataKey])) {
91+
$user = $this->getUserModel();
92+
$user->forceFill($result[$this->userDataKey]);
93+
94+
return $user;
95+
}
96+
97+
return null;
98+
}
99+
100+
public function getUserModel(): Authenticatable
101+
{
102+
$class = $this->getUserModelClass();
103+
104+
return new $class();
105+
}
106+
107+
public function getUserModelClass(): string
108+
{
109+
return $this->userModelClass;
110+
}
111+
112+
public function verifyToken()
113+
{
114+
if ($this->tokenIsNotActive()) {
115+
throw new AuthenticationException('Invalid token');
116+
}
117+
118+
return $this;
119+
}
120+
}

src/IntrospectClient.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace DesignMyNight\Laravel\OAuth2;
4+
5+
use GuzzleHttp\Client;
6+
use Illuminate\Auth\AuthenticationException;
7+
use Illuminate\Cache\Repository as Cache;
8+
9+
class IntrospectClient
10+
{
11+
protected $accessTokenCacheKey = 'access_token';
12+
13+
protected $cache;
14+
protected $client;
15+
protected $config;
16+
17+
public function __construct(array $config, Cache $cache)
18+
{
19+
$this->cache = $cache;
20+
$this->config = $config;
21+
}
22+
23+
protected function getAccessToken(): string
24+
{
25+
$accessToken = $this->cache->get($this->accessTokenCacheKey);
26+
27+
return $accessToken ?: $this->getNewAccessToken();
28+
}
29+
30+
protected function getClient(): Client
31+
{
32+
if ($this->client === null) {
33+
$this->setClient(new Client());
34+
}
35+
36+
return $this->client;
37+
}
38+
39+
protected function getNewAccessToken(): string
40+
{
41+
$response = $this->getClient()->post($this->config['token_url'], [
42+
'form_params' => [
43+
'grant_type' => 'client_credentials',
44+
'client_id' => $this->config['client_id'],
45+
'client_secret' => $this->config['client_secret'],
46+
'scope' => $this->config['scope'],
47+
],
48+
]);
49+
50+
$result = json_decode((string) $response->getBody(), true);
51+
52+
if (isset($result['access_token'])) {
53+
$accessToken = $result['access_token'];
54+
55+
$this->cache->add($this->accessTokenCacheKey, $accessToken, intVal($result['expires_in']) / 60);
56+
57+
return $accessToken;
58+
}
59+
60+
throw new AuthenticationException('Did not receive an access token');
61+
}
62+
63+
public function introspect(string $token)
64+
{
65+
$response = $this->getClient()->post($this->config['introspect_url'], [
66+
'form_params' => [
67+
'token_type_hint' => 'access_token',
68+
'token' => $token,
69+
],
70+
'headers' => [
71+
'Accept' => 'application/json',
72+
'Authorization' => 'Bearer ' . $this->getAccessToken(),
73+
],
74+
]);
75+
76+
return json_decode((string) $response->getBody(), true);
77+
}
78+
79+
public function setClient(Client $client): self
80+
{
81+
$this->client = $client;
82+
83+
return $this;
84+
}
85+
}

src/IntrospectMiddlewareServiceProvider.php

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
<?php
2+
23
namespace DesignMyNight\Laravel\OAuth2;
34

5+
use Illuminate\Cache\Repository as Cache;
46
use Illuminate\Foundation\Application as LaravelApplication;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Support\Facades\Auth;
9+
use Illuminate\Support\Facades\Config;
510
use Illuminate\Support\ServiceProvider;
611
use Laravel\Lumen\Application as LumenApplication;
712

@@ -21,14 +26,29 @@ public function boot()
2126

2227
$this->loadRoutesFrom($routes);
2328
$this->mergeConfigFrom($source, 'authorizationserver');
29+
30+
$this->bootIntrospection();
2431
}
2532

26-
/**
27-
* Register the service provider.
28-
*
29-
* @return void
30-
*/
31-
public function register()
33+
protected function bootIntrospection()
3234
{
35+
$request = $this->app->make(Request::class);
36+
$cache = $this->app->make(Cache::class);
37+
$config = Config::get('authorizationserver');
38+
39+
$client = new IntrospectClient($config, $cache);
40+
$introspect = new Introspect($client, $request);
41+
42+
$this->app->singleton(IntrospectClient::class, function() use($client) {
43+
return $client;
44+
});
45+
46+
$this->app->singleton(Introspect::class, function() use($introspect) {
47+
return $introspect;
48+
});
49+
50+
Auth::extend('introspect', function () use($introspect) {
51+
return new Guard\IntrospectGuard($introspect);
52+
});
3353
}
3454
}

0 commit comments

Comments
 (0)