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