1
+ <?php
2
+
3
+ namespace Ipunkt \Laravel \OAuthIntrospection \Http \Controllers ;
4
+
5
+ use Illuminate \Http \JsonResponse ;
6
+ use Laravel \Passport \Bridge \AccessTokenRepository ;
7
+ use Laravel \Passport \Passport ;
8
+ use Lcobucci \JWT \Parser ;
9
+ use Lcobucci \JWT \Token ;
10
+ use Lcobucci \JWT \ValidationData ;
11
+ use League \OAuth2 \Server \Exception \OAuthServerException ;
12
+ use League \OAuth2 \Server \ResourceServer ;
13
+ use Psr \Http \Message \ResponseInterface ;
14
+ use Psr \Http \Message \ServerRequestInterface ;
15
+ use Zend \Diactoros \Response as Psr7Response ;
16
+
17
+ class IntrospectionController
18
+ {
19
+ /**
20
+ * @var \Lcobucci\JWT\Parser
21
+ */
22
+ private $ jwt ;
23
+
24
+ /**
25
+ * @var \League\OAuth2\Server\ResourceServer
26
+ */
27
+ private $ resourceServer ;
28
+
29
+ /**
30
+ * @var \Laravel\Passport\Bridge\AccessTokenRepository
31
+ */
32
+ private $ accessTokenRepository ;
33
+
34
+ /**
35
+ * constructing IntrospectionController
36
+ *
37
+ * @param \Lcobucci\JWT\Parser $jwt
38
+ * @param \League\OAuth2\Server\ResourceServer $resourceServer
39
+ * @param \Laravel\Passport\Bridge\AccessTokenRepository $accessTokenRepository
40
+ */
41
+ public function __construct (
42
+ Parser $ jwt ,
43
+ ResourceServer $ resourceServer ,
44
+ AccessTokenRepository $ accessTokenRepository
45
+ ) {
46
+ $ this ->jwt = $ jwt ;
47
+ $ this ->resourceServer = $ resourceServer ;
48
+ $ this ->accessTokenRepository = $ accessTokenRepository ;
49
+ }
50
+
51
+ /**
52
+ * Authorize a client to access the user's account.
53
+ *
54
+ * @param ServerRequestInterface $request
55
+ *
56
+ * @return JsonResponse|ResponseInterface
57
+ */
58
+ public function introspectToken (ServerRequestInterface $ request )
59
+ {
60
+ try {
61
+ $ this ->resourceServer ->validateAuthenticatedRequest ($ request );
62
+
63
+ if (array_get ($ request ->getParsedBody (), 'token_type_hint ' , 'access_token ' ) !== 'access_token ' ) {
64
+ // unsupported introspection
65
+ return $ this ->notActiveResponse ();
66
+ }
67
+
68
+ $ accessToken = array_get ($ request ->getParsedBody (), 'token ' );
69
+ if ($ accessToken === null ) {
70
+ return $ this ->notActiveResponse ();
71
+ }
72
+
73
+ $ token = $ this ->jwt ->parse ($ accessToken );
74
+ if ( ! $ this ->verifyToken ($ token )) {
75
+ return $ this ->errorResponse ([
76
+ 'error ' => [
77
+ 'title ' => 'Token invalid '
78
+ ]
79
+ ]);
80
+ }
81
+
82
+ /** @var string $userModel */
83
+ $ userModel = config ('auth.providers.users.model ' );
84
+ $ user = (new $ userModel )->findOrFail ($ token ->getClaim ('sub ' ));
85
+
86
+ return $ this ->jsonResponse ([
87
+ 'active ' => true ,
88
+ 'scope ' => trim (implode (' ' , (array )$ token ->getClaim ('scopes ' , []))),
89
+ 'client_id ' => intval ($ token ->getClaim ('aud ' )),
90
+ 'username ' => $ user ->email ,
91
+ 'token_type ' => 'access_token ' ,
92
+ 'exp ' => intval ($ token ->getClaim ('exp ' )),
93
+ 'iat ' => intval ($ token ->getClaim ('iat ' )),
94
+ 'nbf ' => intval ($ token ->getClaim ('nbf ' )),
95
+ 'sub ' => intval ($ token ->getClaim ('sub ' )),
96
+ 'aud ' => intval ($ token ->getClaim ('aud ' )),
97
+ 'jti ' => $ token ->getClaim ('jti ' ),
98
+ ]);
99
+ } catch (OAuthServerException $ oAuthServerException ) {
100
+ return $ oAuthServerException ->generateHttpResponse (new Psr7Response );
101
+ } catch (\Exception $ exception ) {
102
+ return $ this ->exceptionResponse ($ exception );
103
+ }
104
+ }
105
+
106
+ /**
107
+ * returns inactive token message
108
+ *
109
+ * @return \Illuminate\Http\JsonResponse
110
+ */
111
+ private function notActiveResponse (): JsonResponse
112
+ {
113
+ return $ this ->jsonResponse (['active ' => false ]);
114
+ }
115
+
116
+ /**
117
+ * @param array|mixed $data
118
+ * @param int $status
119
+ *
120
+ * @return \Illuminate\Http\JsonResponse
121
+ */
122
+ private function jsonResponse ($ data , $ status = 200 ): JsonResponse
123
+ {
124
+ return new JsonResponse ($ data , $ status );
125
+ }
126
+
127
+ private function verifyToken (Token $ token ): bool
128
+ {
129
+ $ signer = new \Lcobucci \JWT \Signer \Rsa \Sha256 ();
130
+ $ publicKey = 'file:// ' . Passport::keyPath ('oauth-public.key ' );
131
+
132
+ try {
133
+ if ( ! $ token ->verify ($ signer , $ publicKey )) {
134
+ return false ;
135
+ }
136
+
137
+ $ data = new ValidationData ();
138
+ $ data ->setCurrentTime (time ());
139
+
140
+ if ( ! $ token ->validate ($ data )) {
141
+ return false ;
142
+ }
143
+
144
+ // is token revoked?
145
+ if ($ this ->accessTokenRepository ->isAccessTokenRevoked ($ token ->getClaim ('jti ' ))) {
146
+ return false ;
147
+ }
148
+
149
+ return true ;
150
+ } catch (\Exception $ exception ) {
151
+ }
152
+
153
+ return false ;
154
+ }
155
+
156
+ /**
157
+ * @param array $data
158
+ * @param int $status
159
+ *
160
+ * @return \Illuminate\Http\JsonResponse
161
+ */
162
+ private function errorResponse ($ data , $ status = 400 ): JsonResponse
163
+ {
164
+ return $ this ->jsonResponse ($ data , $ status );
165
+ }
166
+
167
+ /**
168
+ * returns an error
169
+ *
170
+ * @param \Exception $exception
171
+ * @param int $status
172
+ *
173
+ * @return \Illuminate\Http\JsonResponse
174
+ */
175
+ private function exceptionResponse (\Exception $ exception , $ status = 500 ): JsonResponse
176
+ {
177
+ return $ this ->errorResponse ([
178
+ 'error ' => [
179
+ 'id ' => str_slug (get_class ($ exception ) . ' ' . $ status ),
180
+ 'status ' => $ status ,
181
+ 'title ' => $ exception ->getMessage (),
182
+ 'detail ' => $ exception ->getTraceAsString ()
183
+ ],
184
+ ], $ status );
185
+ }
186
+ }
0 commit comments