Skip to content

Commit

Permalink
HTTP Basic Authentication provides a quick way to authenticate users (#…
Browse files Browse the repository at this point in the history
…33)

* Implement auth basic

* remove unused package

* type hint docblock

* [ci skip] update changelog

* Fix request authorization basic

* More test
  • Loading branch information
agungsugiarto authored Sep 28, 2022
1 parent 212dc57 commit 3ec61e7
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 12 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Release Notes

## [Unreleased](https://github.com/agungsugiarto/codeigniter4-authentication/compare/v2.0.0...2.x)
## [Unreleased](https://github.com/agungsugiarto/codeigniter4-authentication/compare/v2.0.1...2.x)

## [v2.0.1 (2022-09-28)](https://github.com/agungsugiarto/codeigniter4-authentication/compare/v2.0.0...v2.0.1)
### Added
* Implement HTTP Basic Authentication provides a quick way to authenticate users by @agungsugiarto in [#32](https://github.com/agungsugiarto/codeigniter4-authentication/pull/32)

## [v2.0.0 (2022-04-11)](https://github.com/agungsugiarto/codeigniter4-authentication/compare/v1.0.8...v2.0.0)
### What's Changed
Expand Down
5 changes: 1 addition & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
},
"require-dev": {
"fakerphp/faker": "^1.13",
"phpunit/phpunit": "^9.1",
"laminas/laminas-coding-standard": "^2.1"
"phpunit/phpunit": "^9.1"
},
"autoload": {
"psr-4": {
Expand All @@ -49,8 +48,6 @@
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"test": "phpunit"
}
}
46 changes: 43 additions & 3 deletions docs/en/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
- [Manually Authenticating Users](#authenticating-users)
- [Remembering Users](#remembering-users)
- [Other Authentication Methods](#other-authentication-methods)
- [HTTP Basic Authentication](#http-basic-authentication)
- [Stateless HTTP Basic Authentication](#stateless-http-basic-authentication)
- [Logging Out](#logging-out)
- [Password Confirmation](#password-confirmation)
- [Configuration](#password-confirmation-configuration)
Expand Down Expand Up @@ -104,9 +106,10 @@ Open `app\Config\FIlters` see property with `aliases` and add this array to regi
```php
public $aliases = [
// ...
'auth' => \Fluent\Auth\Filters\AuthenticationFilter::class,
'can' => \Fluent\Auth\Filters\AuthorizeFilter::class,
'confirm' => [
'auth' => \Fluent\Auth\Filters\AuthenticationFilter::class,
'auth.basic' => \Fluent\Auth\Filters\AuthenticationBasicFilter::class,
'can' => \Fluent\Auth\Filters\AuthorizeFilter::class,
'confirm' => [
\Fluent\Auth\Filters\AuthenticationFilter::class,
\Fluent\Auth\Filters\ConfirmPasswordFilter::class,
],
Expand Down Expand Up @@ -324,6 +327,43 @@ You may pass a boolean value as the second argument to the `loginById` method. T
Auth::loginById(1, $remember = true);
```

<a name="http-basic-authentication"></a>
## HTTP Basic Authentication

[HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) provides a quick way to authenticate users of your application without setting up a dedicated "login" page. To get started, attach the `auth.basic` filter to a route:

```php
$routes->group('basic', ['filter' => "auth.basic:web,email,basic"], function ($routes) {
$routes->get('treasure', function () {
// Only authenticated users may access this route...
});
});
```

Once the filter has been attached to the route, you will automatically be prompted for credentials when accessing the route in your browser. By default, the `auth.basic` filter will assume the `email` column on your `users` database table is the user's "username".

<a name="a-note-on-fastcgi"></a>
#### A Note On FastCGI

If you are using PHP FastCGI and Apache to serve your Laravel application, HTTP Basic authentication may not work correctly. To correct these problems, the following lines may be added to your application's `.htaccess` file:

```apache
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
```

<a name="stateless-http-basic-authentication"></a>
### Stateless HTTP Basic Authentication

You may also use HTTP Basic Authentication without setting a user identifier cookie in the session. This is primarily helpful if you choose to use HTTP Authentication to authenticate requests to your application's API. To accomplish this calls the `onceBasic` method. If no response is returned by the `onceBasic` method, the request may be passed further into the application:
```php
$routes->group('onceBasic', ['filter' => "auth.basic:web,email,onceBasic"], function ($routes) {
$routes->get('treasure', function () {
// Only authenticated users may access this route...
});
});
```

## Logging Out

To manually log users out of your application, you may use the `logout` method provided by the `Auth` facade. This will remove the authentication information from the user's session so that subsequent requests are not authenticated.
Expand Down
107 changes: 106 additions & 1 deletion src/Adapters/SessionAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Session\SessionInterface;
use Exception;
use Fluent\Auth\Contracts\AuthenticationBasicInterface;
use Fluent\Auth\Contracts\AuthenticationInterface;
use Fluent\Auth\Contracts\AuthenticatorInterface;
use Fluent\Auth\Contracts\UserProviderInterface;
use Fluent\Auth\CookieRecaller;
use Fluent\Auth\Exceptions\AuthenticationException;
use Fluent\Auth\Traits\GuardHelperTrait;

use function bin2hex;
use function is_null;
use function random_bytes;
use function sha1;

class SessionAdapter implements AuthenticationInterface
class SessionAdapter implements AuthenticationBasicInterface, AuthenticationInterface
{
use GuardHelperTrait;

Expand Down Expand Up @@ -121,6 +123,109 @@ public function validate(array $credentials): bool
return $this->hasValidCredentials($user, $credentials);
}

/**
* {@inheritdoc}
*/
public function basic($field = 'email', $extraConditions = [])
{
if ($this->check()) {
return;
}

// If a username is set on the HTTP basic request, we will return out without
// interrupting the request lifecycle. Otherwise, we'll need to generate a
// request indicating that the given credentials were invalid for login.
if ($this->attemptBasic($field, $extraConditions)) {
return;
}

throw new AuthenticationException('Invalid credentials.');
}

/**
* {@inheritdoc}
*/
public function onceBasic($field = 'email', $extraConditions = [])
{
$credentials = $this->basicCredentials($field);

if (! $this->once(array_merge($credentials, $extraConditions))) {
throw new AuthenticationException('Invalid credentials.');
}
}

/**
* {@inheritdoc}
*/
public function once(array $credentials = [])
{
Events::trigger('fireAttemptEvent', $credentials);

if ($this->validate($credentials)) {
$this->setUser($this->lastAttempted);

return true;
}

return false;
}

/**
* Attempt to authenticate using basic authentication.
*
* @param string $field
* @param array $extraConditions
* @return bool
*/
protected function attemptBasic($field, $extraConditions = [])
{
return $this->attempt(array_merge(
$this->basicCredentials($field),
$extraConditions
));
}

/**
* Get the credential array for an HTTP Basic request.
*
* @param string $field
* @return array
*/
protected function basicCredentials($field)
{
if (! $this->request->hasHeader('Authorization')) {
return [];
}

$authHeaders = [$this->request->header('Authorization')->getValue()];

if (1 !== count($authHeaders)) {
return [];
}

$authHeader = array_shift($authHeaders);

if (! preg_match('/Basic (?P<credentials>.+)/', $authHeader, $match)) {
return [];
}

$decodedCredentials = base64_decode($match['credentials'], true);

if (false === $decodedCredentials) {
return [];
}

$credentialParts = explode(':', $decodedCredentials, 2);

if (2 !== count($credentialParts)) {
return [];
}

[$username, $password] = $credentialParts;

return [$field => $username, 'password' => $password];
}

/**
* {@inheritdoc}
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Contracts/AuthFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function getDefaultUserProvider();
* Attempt to get the guard from the local cache.
*
* @param string|null $name
* @return AuthenticationInterface
* @return AuthenticationBasicInterface|AuthenticationInterface
*/
public function guard($name = null);

Expand Down
32 changes: 32 additions & 0 deletions src/Contracts/AuthenticationBasicInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Fluent\Auth\Contracts;

interface AuthenticationBasicInterface
{
/**
* Attempt to authenticate using HTTP Basic Auth.
*
* @param string $field
* @param array $extraConditions
* @throws \Fluent\Auth\Exceptions\AuthenticationException
*/
public function basic($field = 'email', $extraConditions = []);

/**
* Perform a stateless HTTP Basic login attempt.
*
* @param string $field
* @param array $extraConditions
* @throws \Fluent\Auth\Exceptions\AuthenticationException
*/
public function onceBasic($field = 'email', $extraConditions = []);

/**
* Log a user into the application without sessions or cookies.
*
* @param array $credentials
* @return bool
*/
public function once(array $credentials = []);
}
3 changes: 2 additions & 1 deletion src/Facades/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Closure;
use Fluent\Auth\Config\Services;
use CodeIgniter\Router\RouteCollection;
use Fluent\Auth\Contracts\AuthenticationBasicInterface;
use Fluent\Auth\Contracts\AuthenticationInterface;
use Fluent\Auth\Contracts\AuthenticatorInterface;
use Fluent\Auth\Contracts\HasAccessTokensInterface;
Expand All @@ -18,7 +19,7 @@
*
* @method static UserProviderInterface createUserProvider($provider = null)
* @method static string getDefaultUserProvider()
* @method static AuthenticationInterface guard($name = null)
* @method static AuthenticationBasicInterface|AuthenticationInterface guard($name = null)
* @method static string getDefaultDriver()
* @method static $this setDefaultDriver($name)
* @method static Closure userResolver()
Expand Down
39 changes: 39 additions & 0 deletions src/Filters/AuthenticationBasicFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Fluent\Auth\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Fluent\Auth\Config\Services;
use Fluent\Auth\Contracts\AuthenticationBasicInterface;
use Fluent\Auth\Contracts\AuthenticationInterface;
use Fluent\Auth\Contracts\AuthFactoryInterface;

class AuthenticationBasicFilter implements FilterInterface
{
/** @var AuthFactoryInterface|AuthenticationBasicInterface|AuthenticationInterface */
protected $auth;

public function __construct()
{
$this->auth = Services::auth();
}

/**
* {@inheritdoc}
*/
public function before(RequestInterface $request, $arguments = null)
{
[$guard, $field, $method] = $arguments;

$this->auth->guard($guard)->{$method}($field);
}

/**
* {@inheritdoc}
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
}
}
3 changes: 2 additions & 1 deletion src/Helpers/auth_helper.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Fluent\Auth\Config\Services;
use Fluent\Auth\Contracts\AuthenticationBasicInterface;
use Fluent\Auth\Contracts\AuthenticationInterface;
use Fluent\Auth\Contracts\AuthFactoryInterface;

Expand All @@ -9,7 +10,7 @@
* Provides convenient access to the main authentication class.
*
* @param string|null $guard
* @return AuthFactoryInterface|AuthenticationInterface
* @return AuthFactoryInterface|AuthenticationBasicInterface|AuthenticationInterface
*/
function auth($guard = null)
{
Expand Down
Loading

0 comments on commit 3ec61e7

Please sign in to comment.