Skip to content

Instantly share code, notes, and snippets.

@lordhasyim
Forked from eusonlito/README.md
Created August 14, 2020 14:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lordhasyim/1b8544475a16366cc492499a95d809a2 to your computer and use it in GitHub Desktop.
Save lordhasyim/1b8544475a16366cc492499a95d809a2 to your computer and use it in GitHub Desktop.
Laravel Auth and Session without database. Using a remote API as Auth and Data provider.

This is an example of a web that uses a remote API as a database wrapper.

The remote API is stateless and the web uses cookies to maintain session persistence.

The API authentication endpoint returns a TOKEN that allows the web to make each request to the API with the user authentication header.


Este es un ejemplo de web que usa una API remota como wrapper de base de datos.

La API remota es stateless y la web usa cookies para mantener la persistencia de sesión.

El endpoint de autenticación de la API devuelve un TOKEN que permite a la web realizar cada petición a la API con la cabecera de autenticación de usuario.

<?php declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\View\View;
use App\Repositories;
class Dashboard extends ControllerInterface
{
/**
* @return \Illuminate\View\View
*/
public function index(): View
{
return $this->page('dashboard.index', [
'modules' => Repositories\Module::bySection('dashboard')
]);
}
}
<?php declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use App\Services;
class UserSession extends ControllerInterface
{
/**
* @return \Illuminate\View\View
*/
public function auth(): View
{
return $this->page('user.auth');
}
/**
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\RedirectResponse
*/
public function authPost(Request $request): RedirectResponse
{
Services\User\Auth::byCredentials($request->input('user'), $request->input('password'));
return redirect()->route('dashboard.index');
}
}
<?php declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
abstract class ModelInterface extends Model
{
/**
* @var array
*/
protected $guarded = [];
}
<?php declare(strict_types=1);
namespace App\Models;
class Module extends ModelInterface
{
}
<?php declare(strict_types=1);
namespace App\Models;
use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
class User extends ModelInterface implements AuthenticatableContract
{
use Authenticatable;
/**
* @param string
*/
public function getAuthIdentifierName(): string
{
return 'token';
}
/**
* @param string
*/
public function getAuthIdentifier(): string
{
return $this->token;
}
}
<?php declare (strict_types = 1);
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use App\Models;
class App extends ServiceProvider
{
/**
* @return void
*/
public function register(): void
{
$this->singletons();
}
/**
* @return void
*/
protected function singletons(): void
{
$this->app->bind('user', static function (): ?Models\User {
return Auth::user();
});
$this->app->bind('token', static function (): ?string {
return session('token');
});
}
}
<?php declare (strict_types = 1);
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth as BaseAuth;
use App\Services;
class Auth extends ServiceProvider
{
/**
* @return void
*/
public function boot(): void
{
$this->registerPolicies();
$this->provider();
}
/**
* @return void
*/
protected function provider(): void
{
BaseAuth::provider('custom', static function (): Services\Auth\UserProvider {
return new Services\Auth\UserProvider();
});
}
}
<?php declare(strict_types=1);
namespace App\Repositories;
use Illuminate\Support\Collection;
use App\Models;
class Module extends RepositoryInterface
{
/**
* @var string
*/
protected static $device = 'web';
/**
* @param string $section
*
* @return \Illuminate\Support\Collection
*/
public static function bySection(string $section): Collection
{
return collect(static::remote(static::url($section)))->map(static function ($row): Models\Module {
return new Models\Module($row);
});
}
/**
* @param string $section
*
* @return string
*/
protected static function url(string $section): string
{
return '/module/'.static::$device.'/'.$section;
}
}
<?php declare(strict_types=1);
namespace App\Repositories;
abstract class RepositoryInterface
{
/**
* @param string $url
* @param array $data = []
*
* @return array
*/
protected static function remote(string $url, array $data = []): array
{
return remote('GET', $url, $data)->exec()->response('array');
}
}
<?php declare(strict_types=1);
namespace App\Repositories;
use App\Models;
class User extends RepositoryInterface
{
/**
* @return \App\Models\User
*/
public static function detail(): Models\User
{
return new Models\User(remote('GET', '/user')->exec()->response('array'));
}
}
<?php declare(strict_types=1);
namespace App\Services\Auth;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider as AuthProvider;
use App\Repositories;
class UserProvider implements AuthProvider
{
public function retrieveById($identifier): Authenticatable
{
return Repositories\User::detail();
}
public function retrieveByToken($identifier, $token)
{
}
public function updateRememberToken(Authenticatable $user, $token)
{
}
public function retrieveByCredentials(array $credentials)
{
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
}
}
<?php declare(strict_types = 1);
namespace App\Services\Curl;
use Exception;
class Curl
{
/**
* @var resource
*/
protected $curl;
/**
* @var string
*/
protected $url = '';
/**
* @var string
*/
protected $method = 'GET';
/**
* @var array
*/
protected $headers = [];
/**
* @var array
*/
protected $data = [];
/**
* @var string
*/
protected $response = '';
/**
* @var array
*/
protected $info = [];
/**
* @return self
*/
public function __construct()
{
$this->init();
}
/**
* @return self
*/
protected function init()
{
$this->curl = curl_init();
$this->setOption(CURLOPT_RETURNTRANSFER, true);
$this->setOption(CURLOPT_SSL_VERIFYPEER, false);
$this->setOption(CURLOPT_SSL_VERIFYHOST, false);
$this->setOption(CURLOPT_MAXREDIRS, 5);
$this->setOption(CURLOPT_FOLLOWLOCATION, true);
$this->setOption(CURLOPT_COOKIESESSION, false);
$this->setOption(CURLOPT_USERAGENT, 'example-web');
$this->setHeader('Content-Type', 'application/json');
$this->setTimeOut(30);
}
/**
* @param int $option
* @param mixed $value
*
* @return self
*/
public function setOption(int $option, $value): self
{
curl_setopt($this->curl, $option, $value);
return $this;
}
/**
* @param string $url
*
* @return self
*/
public function setApiUrl(string $url): self
{
return $this->setUrl(config('app.api').$url);
}
/**
* @param string $url
*
* @return self
*/
public function setUrl(string $url): self
{
$this->url = $url;
$this->setOption(CURLOPT_URL, $this->url);
return $this;
}
/**
* @param array $data
*
* @return self
*/
public function setData(array $data): self
{
$this->data = $data;
return $this;
}
/**
* @param string $key
* @param string $value
*
* @return self
*/
public function setHeader(string $key, string $value): self
{
$this->headers[$key] = $value;
return $this;
}
/**
* @param ?string $token
*
* @return self
*/
public function setAuthorization(?string $token): self
{
if ($token) {
$this->setHeader('Authorization', 'Bearer '.$token);
}
return $this;
}
/**
* @param string|array $user
* @param string $password = null
*
* @return self
*/
public function setBasicAuth($user, string $password = null): self
{
if (is_array($user)) {
[$user, $password] = [$user['user'], $user['password']];
}
$this->setOption(CURLOPT_USERPWD, $user.':'.$password);
return $this;
}
/**
* @param string $method
*
* @return self
*/
public function setMethod(string $method): self
{
$this->method = strtoupper($method);
$this->setOption(CURLOPT_CUSTOMREQUEST, $this->method);
return $this;
}
/**
* @param int $timeout
*
* @return self
*/
public function setTimeOut(int $timeout): self
{
$this->setOption(CURLOPT_TIMEOUT, $timeout);
return $this;
}
/**
* @return self
*/
public function writeHeaders(): self
{
if (empty($this->headers)) {
return $this;
}
$this->setOption(CURLOPT_HTTPHEADER, array_map(static function (string $key, string $value) {
return $key.': '.$value;
}, array_keys($this->headers), $this->headers));
return $this;
}
/**
* @return self
*/
public function exec(): self
{
$this->writeHeaders();
if ($this->method === 'GET') {
$this->execGet();
} else {
$this->execPOST();
}
$this->response = curl_exec($this->curl);
$this->info = curl_getinfo($this->curl);
curl_close($this->curl);
if ($this->info['http_code'] !== 200) {
$this->error($this->response);
}
return $this;
}
/**
* @param string $key = ''
*
* @return mixed
*/
public function info(string $key = '')
{
return $key ? $this->info[$key] : $this->info;
}
/**
* @param string $format = null
*
* @return mixed
*/
public function response(string $format = null)
{
switch ($format) {
case 'array':
return json_decode($this->response, true);
case 'object':
return json_decode($this->response);
default:
return $this->response;
}
}
/**
* @return self
*/
protected function execGET(): self
{
if ($this->data) {
$this->setOption(CURLOPT_URL, $this->url.'?'.http_build_query($this->data));
}
return $this;
}
/**
* @return self
*/
protected function execPOST(): self
{
if ($this->data) {
$this->setOption(CURLOPT_POST, true);
$this->setOption(CURLOPT_POSTFIELDS, json_encode($this->data, JSON_PARTIAL_OUTPUT_ON_ERROR));
}
return $this;
}
/**
* @param string $message
*
* @throws \Exception
*/
protected function error(string $message)
{
throw new Exception($message, $this->info['http_code']);
}
}
<?php declare(strict_types=1);
if (!function_exists('remote')) {
/**
* @param string $method
* @param string $url
* @param array $data = []
*
* @return \App\Services\Curl\Curl
*/
function remote(string $method, string $url, array $data = []): App\Services\Curl\Curl
{
return (new App\Services\Curl\Curl)
->setMethod($method)
->setApiUrl($url)
->setData($data)
->setAuthorization(app('token'));
}
}
<?php declare(strict_types=1);
namespace App\Services\User;
use Illuminate\Support\Facades\Auth as BaseAuth;
use App\Models;
class Auth
{
/**
* @param string $user
* @param string $password
*
* @return \App\Models\User
*/
public static function byCredentials(string $user, string $password): Models\User
{
$response = remote('POST', '/user/auth', [
'user' => $user,
'password' => $password
])->exec()->response('array');
$response['user']['token'] = $response['token'];
return static::auth(new Models\User($response['user']));
}
/**
* @param \App\Models\User $user
*
* @return \App\Models\User
*/
protected static function auth(Models\User $user): Models\User
{
static::session($user);
static::bind($user);
static::login($user);
return $user;
}
/**
* @param \App\Models\User $user
*
* @return void
*/
protected static function session(Models\User $user): void
{
session(['token' => $user->token]);
}
/**
* @param \App\Models\User $user
*
* @return void
*/
protected static function bind(Models\User $user): void
{
app()->bind('user', static function () use ($user): Models\User {
return $user;
});
app()->bind('token', static function (): string {
return session('token');
});
}
/**
* @param \App\Models\User $user
*
* @return void
*/
protected static function login(Models\User $user): void
{
BaseAuth::login($user, true);
}
}
{
"name": "example/web",
"type": "project",
"description": "Example Web",
"keywords": [],
"license": "MIT",
"require": {
"php": "^7.3",
"laravel/framework": "^6.0",
"laravel/helpers": "^1.1"
},
"require-dev": {
"facade/ignition": "^1.9",
"nunomaduro/larastan": "^0.4",
"nunomaduro/phpinsights": "^1.9"
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "app/"
},
"files": [
"app/Services/Helper/functions.php"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
],
"post-install-cmd": [
"@php artisan config:cache",
"@php artisan route:cache"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"pre-install-cmd": [
"rm -f bootstrap/cache/*.php"
],
"pre-update-cmd": [
"rm -f bootstrap/cache/*.php"
]
}
}
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'https://example.com'),
'api' => env('APP_API', 'https://api.example.com'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'en',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\App::class,
App\Providers\Auth::class,
App\Providers\Event::class,
App\Providers\Route::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => [],
];
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => 'web',
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session", "token"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'custom'
],
],
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment