Skip to content

Commit

Permalink
Add JwtSource, WithPayload
Browse files Browse the repository at this point in the history
  • Loading branch information
rougin committed Nov 16, 2024
1 parent 4e196f9 commit 67ba57e
Show file tree
Hide file tree
Showing 8 changed files with 480 additions and 10 deletions.
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ interface JwtParserInterface
}
```

If `JwtSource` is used as a source, the `username` field must be updated also from the `Authsum` class based on the query parameter or parsed body where the token exists:
If `JwtSource` is used as a source, the `token` field must be updated also from the `Authsum` class based on the query parameter or parsed body where the token exists:

``` php
// index.php
Expand All @@ -311,11 +311,35 @@ use Rougin\Authsum\Source\JwtSource;

$source = new JwtSource($parser);

$auth = new Authsum($source);

// Search "token" property from the payload ---
$auth->setUsername('token');
$source->setTokenField('token');
// --------------------------------------------

$auth = new Authsum($source);
```

> [!NOTE]
> If `setTokenField` is not specified, its default value is `token`.
Then use the `setUsernameField` to specify the field to be compared against the parsed data from the JSON web token:

``` php
// index.php

use Rougin\Authsum\Authsum;

// ...

$auth = new Authsum($source);

// ...

$auth->setUsernameField('email');

// The $_POST data should contains the ---
// "token" field and the "email" field ---
$valid = $auth->isValid($_POST);
// ---------------------------------------
```

### Creating custom sources
Expand Down Expand Up @@ -404,6 +428,24 @@ interface WithPassword
}
```

Some custom sources may require to use the provided payload instead of `username` and `password` fields (e.g., `JwtSource`). With this, kindly use the `WithPayload` interface:

``` php
namespace Rougin\Authsum\Source;

interface WithPayload
{
/**
* Sets the prepared payload.
*
* @param array<string, string> $payload
*
* @return self
*/
public function setPayload($payload);
}
```

## Changelog

Please see [CHANGELOG][link-changelog] for more information what has changed recently.
Expand Down
18 changes: 12 additions & 6 deletions src/Authsum.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Rougin\Authsum\Source\SourceInterface;
use Rougin\Authsum\Source\WithPassword;
use Rougin\Authsum\Source\WithPayload;
use Rougin\Authsum\Source\WithUsername;
use UnexpectedValueException;

Expand Down Expand Up @@ -135,18 +136,23 @@ public function isValid($payload)
{
$this->payload = $payload;

if ($this->source instanceof WithUsername)
if ($this->source instanceof WithPassword)
{
$this->source->setUsernameField($this->getUsernameField());
$this->source->setPasswordField($this->getPasswordField());

$this->source->setUsernameValue($this->getUsernameValue());
$this->source->setPasswordValue($this->getPasswordValue());
}

if ($this->source instanceof WithPassword)
if ($this->source instanceof WithPayload)
{
$this->source->setPasswordField($this->getPasswordField());
$this->source->setPayload($this->payload);
}

$this->source->setPasswordValue($this->getPasswordValue());
if ($this->source instanceof WithUsername)
{
$this->source->setUsernameField($this->getUsernameField());

$this->source->setUsernameValue($this->getUsernameValue());
}

$valid = $this->source->isValid();
Expand Down
53 changes: 53 additions & 0 deletions src/Source/JwtParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Rougin\Authsum\Source;

/**
* NOTE: This class only parses the JSON web token without validating it.
* Kindly use third-party JSON web token parsed instead (e.g., lcobucci/jwt).
*
* @link https://stackoverflow.com/q/38552003
*
* @package Authsum
*
* @author Rougin Gutib <rougingutib@gmail.com>
*/
class JwtParser implements JwtParserInterface
{
/**
* @link https://www.converticacommerce.com/support-maintenance/security/php-one-liner-decode-jwt-json-web-tokens
*
* Parses the token string.
*
* @param string $token
*
* @return array<string, mixed>
*/
public function parse($token)
{
$items = explode('.', $token);

$parsed = '';

if (isset($items[1]))
{
$parsed = str_replace('-', '+', $items[1]);
}

$parsed = str_replace('_', '/', $parsed);

$decoded = base64_decode($parsed);

/** @var array<string, mixed>|null */
$result = json_decode($decoded, true);

if ($result)
{
return $result;
}

$text = 'Unable to parse an invalid token';

throw new \UnexpectedValueException($text);
}
}
20 changes: 20 additions & 0 deletions src/Source/JwtParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Rougin\Authsum\Source;

/**
* @package Authsum
*
* @author Rougin Gutib <rougingutib@gmail.com>
*/
interface JwtParserInterface
{
/**
* Parses the token string.
*
* @param string $token
*
* @return array<string, mixed>
*/
public function parse($token);
}
143 changes: 143 additions & 0 deletions src/Source/JwtSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

namespace Rougin\Authsum\Source;

use Rougin\Authsum\Source;

/**
* @package Authsum
*
* @author Rougin Gutib <rougingutib@gmail.com>
*/
class JwtSource extends Source implements WithUsername, WithPayload
{
/**
* @var \Rougin\Authsum\Source\JwtParserInterface
*/
protected $parser;

/**
* @var array<string, string>
*/
protected $payload = array();

/**
* @var string
*/
protected $usernameField;

/**
* @var string
*/
protected $usernameValue;

/**
* @var string
*/
protected $token = 'token';

/**
* @param \Rougin\Authsum\Source\JwtParserInterface $parser
*/
public function __construct(JwtParserInterface $parser)
{
$this->parser = $parser;
}

/**
* Checks if it exists from the source.
*
* @return boolean
*/
public function isValid()
{
if (! array_key_exists($this->token, $this->payload))
{
return $this->setNotFound($this->token);
}

$token = $this->payload[$this->token];

try
{
$parsed = $this->parser->parse($token);

if (! array_key_exists($this->usernameField, $parsed))
{
return $this->setNotFound($this->usernameField);
}
}
catch (\Exception $e)
{
return $this->setError($e->getMessage());
}

$same = $parsed[$this->usernameField] === $this->usernameValue;

return $same ? $this->setResult() : $this->setError();
}

/**
* Sets the prepared payload.
*
* @param array<string, string> $payload
*
* @return self
*/
public function setPayload($payload)
{
$this->payload = $payload;

return $this;
}

/**
* Sets the username field.
*
* @param string $username
*
* @return self
*/
public function setUsernameField($username)
{
$this->usernameField = $username;

return $this;
}

/**
* Sets the username value.
*
* @param string $username
*
* @return self
*/
public function setUsernameValue($username)
{
$this->usernameValue = $username;

return $this;
}

/**
* @param string $token
*
* @return self
*/
public function setTokenField($token)
{
$this->token = $token;

return $this;
}

/**
* @param string $name
*
* @return boolean
*/
protected function setNotFound($name)
{
return $this->setError('Field "' . $name . '" not found from payload');
}
}
20 changes: 20 additions & 0 deletions src/Source/WithPayload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Rougin\Authsum\Source;

/**
* @package Authsum
*
* @author Rougin Gutib <rougingutib@gmail.com>
*/
interface WithPayload
{
/**
* Sets the prepared payload.
*
* @param array<string, string> $payload
*
* @return self
*/
public function setPayload($payload);
}
Loading

0 comments on commit 67ba57e

Please sign in to comment.