Skip to content

Commit ddb536c

Browse files
Fix #97: Add HTTP cookie authentication method
1 parent c0d41b3 commit ddb536c

4 files changed

Lines changed: 204 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 3.1.2 under development
44

55
- Enh #92: Use `SensitiveParameter` attribute to mark sensitive parameters (@ev-gor)
6+
- New #97: Add HTTP cookie authentication method (@IbragimovDiyorbek)
67
- Chg #95, #99: Change PHP constraint in `composer.json` to `8.0 - 8.4` (@vjik)
78
- Bug #99: Explicitly mark nullable parameters (@vjik)
89

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ $authenticationMethod = (new \Yiisoft\Auth\Method\QueryParameter($identityReposi
104104
->withParameterName('token');
105105
```
106106

107+
### HTTP cookie authentication
108+
109+
```php
110+
$authenticationMethod = (new \Yiisoft\Auth\Method\HttpCookie($identityRepository))
111+
->withCookieName('access-token');
112+
```
113+
114+
Typical authentication for websites by storing a token in a browser cookie.
115+
107116
### Using multiple authentication methods
108117

109118
To use multiple authentication methods, use `Yiisoft\Auth\Method\Composite`:

src/Method/HttpCookie.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Auth\Method;
6+
7+
use Psr\Http\Message\ResponseInterface;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use Yiisoft\Auth\AuthenticationMethodInterface;
10+
use Yiisoft\Auth\IdentityInterface;
11+
use Yiisoft\Auth\IdentityWithTokenRepositoryInterface;
12+
13+
/**
14+
* HTTP cookie authentication method.
15+
*
16+
* @see https://tools.ietf.org/html/rfc6265
17+
*/
18+
final class HttpCookie implements AuthenticationMethodInterface
19+
{
20+
private string $cookieName = 'access-token';
21+
private ?string $tokenType = null;
22+
23+
public function __construct(
24+
private IdentityWithTokenRepositoryInterface $identityRepository
25+
) {
26+
}
27+
28+
public function authenticate(ServerRequestInterface $request): IdentityInterface|null
29+
{
30+
$authToken = $this->getAuthenticationToken($request);
31+
32+
if ($authToken === null) {
33+
return null;
34+
}
35+
36+
return $this->identityRepository->findIdentityByToken($authToken, $this->tokenType);
37+
}
38+
39+
public function challenge(ResponseInterface $response): ResponseInterface
40+
{
41+
return $response;
42+
}
43+
44+
/**
45+
* @psalm-immutable
46+
*/
47+
public function withCookieName(string $cookieName): self
48+
{
49+
$new = clone $this;
50+
$new->cookieName = $cookieName;
51+
return $new;
52+
}
53+
54+
/**
55+
* @psalm-immutable
56+
*/
57+
public function withTokenType(?string $type): self
58+
{
59+
$new = clone $this;
60+
$new->tokenType = $type;
61+
return $new;
62+
}
63+
64+
private function getAuthenticationToken(ServerRequestInterface $request): ?string
65+
{
66+
$cookies = $request->getCookieParams();
67+
68+
return $cookies[$this->cookieName] ?? null;
69+
}
70+
}

tests/Method/HttpCookieTest.php

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Auth\Tests\Method;
6+
7+
use Nyholm\Psr7\Response;
8+
use Nyholm\Psr7\ServerRequest;
9+
use PHPUnit\Framework\TestCase;
10+
use Psr\Http\Message\ServerRequestInterface;
11+
use Yiisoft\Auth\IdentityInterface;
12+
use Yiisoft\Auth\Method\HttpCookie;
13+
use Yiisoft\Auth\Tests\Stub\FakeIdentity;
14+
use Yiisoft\Auth\Tests\Stub\FakeIdentityRepository;
15+
use Yiisoft\Http\Method;
16+
17+
final class HttpCookieTest extends TestCase
18+
{
19+
public function testSuccessfulAuthentication(): void
20+
{
21+
$identity = $this->createIdentity();
22+
$identityRepository = new FakeIdentityRepository($identity);
23+
$result = (new HttpCookie($identityRepository))->authenticate(
24+
$this->createRequest(['access-token' => 'access-token-value'])
25+
);
26+
27+
$this->assertSame($result, $identity);
28+
$this->assertEquals(
29+
[
30+
'findIdentityByToken' => [
31+
'token' => 'access-token-value',
32+
'type' => null,
33+
],
34+
],
35+
$identityRepository->getCallParams()
36+
);
37+
}
38+
39+
public function testWithoutToken(): void
40+
{
41+
$identity = $this->createIdentity();
42+
$identityRepository = new FakeIdentityRepository($identity);
43+
44+
$result = (new HttpCookie($identityRepository))->authenticate(
45+
$this->createRequest()
46+
);
47+
48+
$this->assertNull($result);
49+
}
50+
51+
public function testIdentityNotFoundByToken(): void
52+
{
53+
$identityRepository = new FakeIdentityRepository(null);
54+
$authenticationMethod = new HttpCookie($identityRepository);
55+
56+
$this->assertNull(
57+
$authenticationMethod->authenticate(
58+
$this->createRequest(['access-token' => 'access-token-value'])
59+
)
60+
);
61+
}
62+
63+
public function testChallengeImmutabilityStatus(): void
64+
{
65+
$response = new Response(400);
66+
$identityRepository = new FakeIdentityRepository($this->createIdentity());
67+
$authenticationMethod = new HttpCookie($identityRepository);
68+
69+
$this->assertSame($response, $authenticationMethod->challenge($response));
70+
}
71+
72+
public function testCustomTokenParam(): void
73+
{
74+
$identityRepository = new FakeIdentityRepository($this->createIdentity());
75+
$authenticationMethod = (new HttpCookie($identityRepository))
76+
->withCookieName('AuthToken');
77+
78+
$result = $authenticationMethod->authenticate(
79+
$this->createRequest(['AuthToken' => 'AccessTokenValue'])
80+
);
81+
82+
$this->assertNotNull($result);
83+
$this->assertEquals('test-id', $result->getId());
84+
}
85+
86+
public function testImmutability(): void
87+
{
88+
$identityRepository = new FakeIdentityRepository($this->createIdentity());
89+
$original = (new HttpCookie($identityRepository));
90+
$this->assertNotSame($original, $original->withCookieName('cookieName'));
91+
$this->assertNotSame($original, $original->withTokenType('another-token-type'));
92+
}
93+
94+
public function testWithTokenType(): void
95+
{
96+
$identityRepository = new FakeIdentityRepository($this->createIdentity());
97+
(new HttpCookie($identityRepository))
98+
->withTokenType('another-token-type')
99+
->authenticate(
100+
$this->createRequest(['access-token' => 'access-token-value'])
101+
);
102+
103+
$this->assertEquals(
104+
[
105+
'findIdentityByToken' => [
106+
'token' => 'access-token-value',
107+
'type' => 'another-token-type',
108+
],
109+
],
110+
$identityRepository->getCallParams()
111+
);
112+
}
113+
114+
private function createIdentity(): IdentityInterface
115+
{
116+
return new FakeIdentity('test-id');
117+
}
118+
119+
private function createRequest(array $cookieParams = []): ServerRequestInterface
120+
{
121+
return (new ServerRequest(Method::GET, '/'))
122+
->withCookieParams($cookieParams);
123+
}
124+
}

0 commit comments

Comments
 (0)