blob: 0ee3708aaa1c6cf52d58339e725acfa1448084d6 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3namespace OAuth2\ResponseType;
4
5use OAuth2\Encryption\EncryptionInterface;
6use OAuth2\Encryption\Jwt;
7use OAuth2\Storage\AccessTokenInterface as AccessTokenStorageInterface;
8use OAuth2\Storage\RefreshTokenInterface;
9use OAuth2\Storage\PublicKeyInterface;
10use OAuth2\Storage\Memory;
11
12/**
13 * @author Brent Shaffer <bshafs at gmail dot com>
14 */
15class JwtAccessToken extends AccessToken
16{
17 protected $publicKeyStorage;
18 protected $encryptionUtil;
19
20 /**
21 * @param PublicKeyInterface $publicKeyStorage -
22 * @param AccessTokenStorageInterface $tokenStorage -
23 * @param RefreshTokenInterface $refreshStorage -
24 * @param array $config - array with key store_encrypted_token_string (bool true)
25 * whether the entire encrypted string is stored,
26 * or just the token ID is stored
27 * @param EncryptionInterface $encryptionUtil -
28 */
29 public function __construct(PublicKeyInterface $publicKeyStorage = null, AccessTokenStorageInterface $tokenStorage = null, RefreshTokenInterface $refreshStorage = null, array $config = array(), EncryptionInterface $encryptionUtil = null)
30 {
31 $this->publicKeyStorage = $publicKeyStorage;
32 $config = array_merge(array(
33 'store_encrypted_token_string' => true,
34 'issuer' => ''
35 ), $config);
36 if (is_null($tokenStorage)) {
37 // a pass-thru, so we can call the parent constructor
38 $tokenStorage = new Memory();
39 }
40 if (is_null($encryptionUtil)) {
41 $encryptionUtil = new Jwt();
42 }
43 $this->encryptionUtil = $encryptionUtil;
44 parent::__construct($tokenStorage, $refreshStorage, $config);
45 }
46
47 /**
48 * Handle the creation of access token, also issue refresh token if supported / desirable.
49 *
50 * @param mixed $client_id - Client identifier related to the access token.
51 * @param mixed $user_id - User ID associated with the access token
52 * @param string $scope - (optional) Scopes to be stored in space-separated string.
53 * @param bool $includeRefreshToken - If true, a new refresh_token will be added to the response
54 * @return array - The access token
55 *
56 * @see http://tools.ietf.org/html/rfc6749#section-5
57 * @ingroup oauth2_section_5
58 */
59 public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
60 {
61 // payload to encrypt
62 $payload = $this->createPayload($client_id, $user_id, $scope);
63
64 /*
65 * Encode the payload data into a single JWT access_token string
66 */
67 $access_token = $this->encodeToken($payload, $client_id);
68
69 /*
70 * Save the token to a secondary storage. This is implemented on the
71 * OAuth2\Storage\JwtAccessToken side, and will not actually store anything,
72 * if no secondary storage has been supplied
73 */
74 $token_to_store = $this->config['store_encrypted_token_string'] ? $access_token : $payload['id'];
75 $this->tokenStorage->setAccessToken($token_to_store, $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);
76
77 // token to return to the client
78 $token = array(
79 'access_token' => $access_token,
80 'expires_in' => $this->config['access_lifetime'],
81 'token_type' => $this->config['token_type'],
82 'scope' => $scope
83 );
84
85 /*
86 * Issue a refresh token also, if we support them
87 *
88 * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface
89 * is supplied in the constructor
90 */
91 if ($includeRefreshToken && $this->refreshStorage) {
92 $refresh_token = $this->generateRefreshToken();
93 $expires = 0;
94 if ($this->config['refresh_token_lifetime'] > 0) {
95 $expires = time() + $this->config['refresh_token_lifetime'];
96 }
97 $this->refreshStorage->setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope);
98 $token['refresh_token'] = $refresh_token;
99 }
100
101 return $token;
102 }
103
104 /**
105 * @param array $token
106 * @param mixed $client_id
107 * @return mixed
108 */
109 protected function encodeToken(array $token, $client_id = null)
110 {
111 $private_key = $this->publicKeyStorage->getPrivateKey($client_id);
112 $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
113
114 return $this->encryptionUtil->encode($token, $private_key, $algorithm);
115 }
116
117 /**
118 * This function can be used to create custom JWT payloads
119 *
120 * @param mixed $client_id - Client identifier related to the access token.
121 * @param mixed $user_id - User ID associated with the access token
122 * @param string $scope - (optional) Scopes to be stored in space-separated string.
123 * @return array - The access token
124 */
125 protected function createPayload($client_id, $user_id, $scope = null)
126 {
127 // token to encrypt
128 $expires = time() + $this->config['access_lifetime'];
129 $id = $this->generateAccessToken();
130
131 $payload = array(
132 'id' => $id, // for BC (see #591)
133 'jti' => $id,
134 'iss' => $this->config['issuer'],
135 'aud' => $client_id,
136 'sub' => $user_id,
137 'exp' => $expires,
138 'iat' => time(),
139 'token_type' => $this->config['token_type'],
140 'scope' => $scope
141 );
142
143 if (isset($this->config['jwt_extra_payload_callable'])) {
144 if (!is_callable($this->config['jwt_extra_payload_callable'])) {
145 throw new \InvalidArgumentException('jwt_extra_payload_callable is not callable');
146 }
147
148 $extra = call_user_func($this->config['jwt_extra_payload_callable'], $client_id, $user_id, $scope);
149
150 if (!is_array($extra)) {
151 throw new \InvalidArgumentException('jwt_extra_payload_callable must return array');
152 }
153
154 $payload = array_merge($extra, $payload);
155 }
156
157 return $payload;
158 }
159}