Skip to content

Commit 7e84c3f

Browse files
authored
Merge pull request from GHSA-j72f-h752-mx4w
fix: raw token logging
2 parents f77c6ae + 725baaa commit 7e84c3f

File tree

12 files changed

+174
-70
lines changed

12 files changed

+174
-70
lines changed

docs/addons/jwt.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ To use JWT Authentication, you need additional setup and configuration.
3434

3535
```php
3636
<?php
37-
37+
3838
// app/Config/AuthJWT.php
39-
39+
4040
declare(strict_types=1);
4141

4242
namespace Config;
@@ -128,6 +128,19 @@ php -r 'echo base64_encode(random_bytes(32));'
128128

129129
The secret key is used for signing and validating tokens.
130130

131+
### Login Attempt Logging
132+
133+
By default, only failed login attempts are recorded in the `auth_token_logins` table.
134+
135+
```php
136+
public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE;
137+
```
138+
139+
If you don't want any logs, set it to `Auth::RECORD_LOGIN_ATTEMPT_NONE`.
140+
141+
If you want to log all login attempts, set it to `Auth::RECORD_LOGIN_ATTEMPT_ALL`.
142+
It means you log all requests.
143+
131144
## Issuing JWTs
132145

133146
To use JWT Authentication, you need a controller that issues JWTs.
@@ -351,3 +364,14 @@ It uses the `secret` and `alg` in the `Config\AuthJWT::$keys['default']`.
351364
It sets the `Config\AuthJWT::$defaultClaims` to the token, and sets
352365
`"iat"` (Issued At) and `"exp"` (Expiration Time) claims automatically even if
353366
you don't pass them.
367+
368+
## Logging
369+
370+
Login attempts are recorded in the `auth_token_logins` table, according to the
371+
configuration above.
372+
373+
When a failed login attempt is logged, the raw token value sent is saved in
374+
the `identifier` column.
375+
376+
When a successful login attempt is logged, the SHA256 hash value of the token
377+
sent is saved in the `identifier` column.

docs/getting_started/configuration.md

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,8 @@ If you have completed the setup according to this documentation, you will have
88
the following configuration files:
99

1010
- **app/Config/Auth.php**
11-
- **app/Config/AuthGroups.php** - For Authorization
12-
- **app/Config/AuthToken.php** - For AccessTokens and HmacSha256 Authentication
13-
- **app/Config/AuthJWT.php** - For JWT Authentication
11+
- **app/Config/AuthGroups.php** - For [Authorization](../references/authorization.md)
12+
- **app/Config/AuthToken.php** - For [AccessTokens](../references/authentication/tokens.md#configuration) and [HmacSha256](../references/authentication/hmac.md#configuration) Authentication
13+
- **app/Config/AuthJWT.php** - For [JWT Authentication](../addons/jwt.md#configuration)
1414

1515
Note that you do not need to have configuration files for features you do not use.
16-
17-
This section describes the major Config items that are not described elsewhere.
18-
19-
## AccessTokens Authenticator
20-
21-
### Access Token Lifetime
22-
23-
By default, Access Tokens can be used for 1 year since the last use. This can be easily modified in the **app/Config/AuthToken.php** config file.
24-
25-
```php
26-
public int $unusedTokenLifetime = YEAR;
27-
```

docs/references/authentication/hmac.md

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,20 +112,6 @@ $token = $user->getHmacTokenById($id);
112112
$tokens = $user->hmacTokens();
113113
```
114114

115-
## HMAC Keys Lifetime
116-
117-
HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used.
118-
This uses the same configuration value as AccessTokens.
119-
120-
By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime`
121-
value in the **app/Config/AuthToken.php** config file. This is in seconds so that you can use the
122-
[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants)
123-
that CodeIgniter provides.
124-
125-
```php
126-
public $unusedTokenLifetime = YEAR;
127-
```
128-
129115
## HMAC Keys Scopes
130116

131117
Each token (set of keys) can be given one or more scopes they can be used within. These can be thought of as
@@ -219,3 +205,50 @@ authtoken.hmacEncryptionCurrentKey = k2
219205
Depending on the set length of the Secret Key and the type of encryption used, it is possible for the encrypted value to
220206
exceed the database column character limit of 255 characters. If this happens, creation of a new HMAC identity will
221207
throw a `RuntimeException`.
208+
209+
## Configuration
210+
211+
Configure **app/Config/AuthToken.php** for your needs.
212+
213+
!!! note
214+
215+
Shield does not expect you use the Access Token Authenticator and HMAC Authenticator
216+
at the same time. Therefore, some Config items are common.
217+
218+
### HMAC Keys Lifetime
219+
220+
HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used.
221+
222+
By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime`
223+
value. This is in seconds so that you can use the
224+
[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants)
225+
that CodeIgniter provides.
226+
227+
```php
228+
public $unusedTokenLifetime = YEAR;
229+
```
230+
231+
### Login Attempt Logging
232+
233+
By default, only failed login attempts are recorded in the `auth_token_logins` table.
234+
This can be modified by changing the `$recordLoginAttempt` value.
235+
236+
```php
237+
public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE;
238+
```
239+
240+
If you don't want any logs, set it to `Auth::RECORD_LOGIN_ATTEMPT_NONE`.
241+
242+
If you want to log all login attempts, set it to `Auth::RECORD_LOGIN_ATTEMPT_ALL`.
243+
It means you log all requests.
244+
245+
## Logging
246+
247+
Login attempts are recorded in the `auth_token_logins` table, according to the
248+
configuration above.
249+
250+
When a failed login attempt is logged, the raw token value sent is saved in
251+
the `identifier` column.
252+
253+
When a successful login attempt is logged, the token name is saved in the
254+
`identifier` column.

docs/references/authentication/tokens.md

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,6 @@ $token = $user->getAccessTokenById($id);
8383
$tokens = $user->accessTokens();
8484
```
8585

86-
## Access Token Lifetime
87-
88-
Tokens will expire after a specified amount of time has passed since they have been used.
89-
By default, this is set to 1 year. You can change this value by setting the `$unusedTokenLifetime`
90-
value in the **app/Config/AuthToken.php** config file. This is in seconds so that you can use the
91-
[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants)
92-
that CodeIgniter provides.
93-
94-
```php
95-
public $unusedTokenLifetime = YEAR;
96-
```
97-
9886
## Access Token Scopes
9987

10088
Each token can be given one or more scopes they can be used within. These can be thought of as
@@ -125,3 +113,52 @@ if ($user->tokenCant('forums.manage')) {
125113
// do something....
126114
}
127115
```
116+
117+
## Configuration
118+
119+
Configure **app/Config/AuthToken.php** for your needs.
120+
121+
!!! note
122+
123+
Shield does not expect you use the Access Token Authenticator and HMAC Authenticator
124+
at the same time. Therefore, some Config items are common.
125+
126+
### Access Token Lifetime
127+
128+
Tokens will expire after a specified amount of time has passed since they have been used.
129+
130+
By default, this is set to 1 year.
131+
You can change this value by setting the `$unusedTokenLifetime` value. This is
132+
in seconds so that you can use the
133+
[time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants)
134+
that CodeIgniter provides.
135+
136+
```php
137+
public $unusedTokenLifetime = YEAR;
138+
```
139+
140+
### Login Attempt Logging
141+
142+
By default, only failed login attempts are recorded in the `auth_token_logins` table.
143+
144+
This can be modified by changing the `$recordLoginAttempt` value.
145+
146+
```php
147+
public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE;
148+
```
149+
150+
If you don't want any logs, set it to `Auth::RECORD_LOGIN_ATTEMPT_NONE`.
151+
152+
If you want to log all login attempts, set it to `Auth::RECORD_LOGIN_ATTEMPT_ALL`.
153+
It means you log all requests.
154+
155+
## Logging
156+
157+
Login attempts are recorded in the `auth_token_logins` table, according to the
158+
configuration above.
159+
160+
When a failed login attempt is logged, the raw token value sent is saved in
161+
the `identifier` column.
162+
163+
When a successful login attempt is logged, the token name is saved in the
164+
`identifier` column.

phpstan-baseline.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,6 @@
361361
'count' => 1,
362362
'path' => __DIR__ . '/src/Models/UserModel.php',
363363
];
364-
$ignoreErrors[] = [
365-
'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#',
366-
'count' => 2,
367-
'path' => __DIR__ . '/tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php',
368-
];
369364
$ignoreErrors[] = [
370365
'message' => '#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with \'CodeIgniter\\\\\\\\Shield\\\\\\\\Result\' and CodeIgniter\\\\Shield\\\\Result will always evaluate to true\\.$#',
371366
'count' => 3,

src/Authentication/Authenticators/AccessTokens.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,15 @@ public function attempt(array $credentials): Result
7777
return $result;
7878
}
7979

80-
$user = $result->extraInfo();
80+
$user = $result->extraInfo();
81+
$token = $user->getAccessToken($this->getBearerToken());
8182

8283
if ($user->isBanned()) {
8384
if ($config->recordLoginAttempt >= Auth::RECORD_LOGIN_ATTEMPT_FAILURE) {
8485
// Record a banned login attempt.
8586
$this->loginModel->recordLoginAttempt(
8687
self::ID_TYPE_ACCESS_TOKEN,
87-
$credentials['token'] ?? '',
88+
$token->name ?? '',
8889
false,
8990
$ipAddress,
9091
$userAgent,
@@ -100,17 +101,15 @@ public function attempt(array $credentials): Result
100101
]);
101102
}
102103

103-
$user = $user->setAccessToken(
104-
$user->getAccessToken($this->getBearerToken())
105-
);
104+
$user = $user->setAccessToken($token);
106105

107106
$this->login($user);
108107

109108
if ($config->recordLoginAttempt === Auth::RECORD_LOGIN_ATTEMPT_ALL) {
110109
// Record a successful login attempt.
111110
$this->loginModel->recordLoginAttempt(
112111
self::ID_TYPE_ACCESS_TOKEN,
113-
$credentials['token'] ?? '',
112+
$token->name ?? '',
114113
true,
115114
$ipAddress,
116115
$userAgent,

src/Authentication/Authenticators/HmacSha256.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,15 @@ public function attempt(array $credentials): Result
7878
return $result;
7979
}
8080

81-
$user = $result->extraInfo();
81+
$user = $result->extraInfo();
82+
$token = $user->getHmacToken($this->getHmacKeyFromToken());
8283

8384
if ($user->isBanned()) {
8485
if ($config->recordLoginAttempt >= Auth::RECORD_LOGIN_ATTEMPT_FAILURE) {
8586
// Record a banned login attempt.
8687
$this->loginModel->recordLoginAttempt(
8788
self::ID_TYPE_HMAC_TOKEN,
88-
$credentials['token'] ?? '',
89+
$token->name ?? '',
8990
false,
9091
$ipAddress,
9192
$userAgent,
@@ -101,17 +102,15 @@ public function attempt(array $credentials): Result
101102
]);
102103
}
103104

104-
$user = $user->setHmacToken(
105-
$user->getHmacToken($this->getHmacKeyFromToken())
106-
);
105+
$user = $user->setHmacToken($token);
107106

108107
$this->login($user);
109108

110109
if ($config->recordLoginAttempt === Auth::RECORD_LOGIN_ATTEMPT_ALL) {
111110
// Record a successful login attempt.
112111
$this->loginModel->recordLoginAttempt(
113112
self::ID_TYPE_HMAC_TOKEN,
114-
$credentials['token'] ?? '',
113+
$token->name ?? '',
115114
true,
116115
$ipAddress,
117116
$userAgent,

src/Authentication/Authenticators/JWT.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public function attempt(array $credentials): Result
103103
// Record a banned login attempt.
104104
$this->tokenLoginModel->recordLoginAttempt(
105105
self::ID_TYPE_JWT,
106-
$credentials['token'] ?? '',
106+
'sha256:' . hash('sha256', $credentials['token'] ?? ''),
107107
false,
108108
$ipAddress,
109109
$userAgent,
@@ -125,7 +125,7 @@ public function attempt(array $credentials): Result
125125
// Record a successful login attempt.
126126
$this->tokenLoginModel->recordLoginAttempt(
127127
self::ID_TYPE_JWT,
128-
$credentials['token'] ?? '',
128+
'sha256:' . hash('sha256', $credentials['token']),
129129
true,
130130
$ipAddress,
131131
$userAgent,

src/Config/AuthToken.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class AuthToken extends BaseAuthToken
4646

4747
/**
4848
* --------------------------------------------------------------------
49-
* Unused Token Lifetime
49+
* Unused Token Lifetime for Token Auth and HMAC Auth
5050
* --------------------------------------------------------------------
5151
* Determines the amount of time, in seconds, that an unused token can
5252
* be used.

tests/Authentication/Authenticators/AccessTokenAuthenticatorTest.php

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
use CodeIgniter\Shield\Authentication\Authentication;
1818
use CodeIgniter\Shield\Authentication\Authenticators\AccessTokens;
1919
use CodeIgniter\Shield\Config\Auth;
20+
use CodeIgniter\Shield\Config\AuthToken;
2021
use CodeIgniter\Shield\Entities\AccessToken;
2122
use CodeIgniter\Shield\Entities\User;
2223
use CodeIgniter\Shield\Models\UserIdentityModel;
2324
use CodeIgniter\Shield\Models\UserModel;
24-
use CodeIgniter\Shield\Result;
2525
use CodeIgniter\Test\Mock\MockEvents;
2626
use Config\Services;
2727
use Tests\Support\DatabaseTestCase;
@@ -182,7 +182,6 @@ public function testAttemptCannotFindUser(): void
182182
'token' => 'abc123',
183183
]);
184184

185-
$this->assertInstanceOf(Result::class, $result);
186185
$this->assertFalse($result->isOK());
187186
$this->assertSame(lang('Auth.badToken'), $result->reason());
188187

@@ -205,7 +204,6 @@ public function testAttemptSuccess(): void
205204
'token' => $token->raw_token,
206205
]);
207206

208-
$this->assertInstanceOf(Result::class, $result);
209207
$this->assertTrue($result->isOK());
210208

211209
$foundUser = $result->extraInfo();
@@ -222,6 +220,37 @@ public function testAttemptSuccess(): void
222220
]);
223221
}
224222

223+
public function testAttemptSuccessLog(): void
224+
{
225+
// Change $recordLoginAttempt in Config.
226+
/** @var AuthToken $config */
227+
$config = config('AuthToken');
228+
$config->recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_ALL;
229+
230+
/** @var User $user */
231+
$user = fake(UserModel::class);
232+
$token = $user->generateAccessToken('foo');
233+
$this->setRequestHeader($token->raw_token);
234+
235+
$result = $this->auth->attempt([
236+
'token' => $token->raw_token,
237+
]);
238+
239+
$this->assertTrue($result->isOK());
240+
241+
$foundUser = $result->extraInfo();
242+
$this->assertInstanceOf(User::class, $foundUser);
243+
$this->assertSame($user->id, $foundUser->id);
244+
$this->assertInstanceOf(AccessToken::class, $foundUser->currentAccessToken());
245+
$this->assertSame($token->token, $foundUser->currentAccessToken()->token);
246+
247+
$this->seeInDatabase($this->tables['token_logins'], [
248+
'id_type' => AccessTokens::ID_TYPE_ACCESS_TOKEN,
249+
'identifier' => 'foo',
250+
'success' => 1,
251+
]);
252+
}
253+
225254
protected function setRequestHeader(string $token): void
226255
{
227256
$request = service('request');

0 commit comments

Comments
 (0)