abc-da-construcao/autenticacao-package

v1.10.1 2022-09-23 17:13 UTC

README

Pacote de recursos Laravel e Lumen para auxiliar na implementação do Sistema de Autenticação Global (SAG).

Este pacote para Lumen e Laravel fornece um driver customizado de autenticação/autorização, middleware, Facade e commands para uso do Sistema de Autenticação Global nos padrões ABC.



Instalação

Use o comando abaixo.

composer require abc-da-construcao/autenticacao-package

Configuração

Geral

Preencha as chaves APP_NAME e APP_KEY contidas no arquivo .env do projeto conforme cadastro no SAG.

APP_NAME="api_pedidos-production"
APP_KEY=Gb4E7xqR74Pat9gefb7nidcWFZNW8S66

Laravel

Abra o arquivo config/auth.php e altere o driver de autenticação para jwt e comente as linhas correspondentes ao provider.

//...
'guards' => [
    // ...
    'sag' => [
        'driver' => 'sag-jwt'
    ],
]
//...

Lumen

Copie os seguintes arquivos para o diretório config do seu projeto. Crie o diretório caso não exista.

vendor/laravel/lumen-framework/config/auth.php

Abra o arquivo config/auth.php e altere o driver de autenticação para jwt.

'guards' => [
    // ...
    'sag' => ['driver' => 'sag-jwt'],
]

Garanta que no arquivo bootstrap/app.php exista as seguintes configurações.

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| Here we will load the environment and create the application instance
| that serves as the central piece of this framework. We'll use this
| application as an "IoC" container and router for this framework.
|
*/

// ...
$app->withFacades();
// ...

/*
|--------------------------------------------------------------------------
| Register Config Files
|--------------------------------------------------------------------------
|
| Now we will register the "app" configuration file. If the file exists in
| your configuration directory it will be loaded; otherwise, we'll load
| the default version. You may register other files below as needed.
|
*/

// ...
$app->configure('auth');
// ...

/*
|--------------------------------------------------------------------------
| Register Middleware
|--------------------------------------------------------------------------
|
| Next, we will register the middleware with the application. These can
| be global middleware that run before and after each request into a
| route or middleware that'll be assigned to some specific routes.
|
*/

// ...
$app->routeMiddleware([
    'auth' => App\Http\Middleware\Authenticate::class,
]);
// ...

/*
|--------------------------------------------------------------------------
| Register Service Providers
|--------------------------------------------------------------------------
|
| Here we will register all of the application's service providers which
| are used to bind services into the container. Service providers are
| totally optional, so you are not required to uncomment this line.
|
*/

// ...
// $app->register(App\Providers\AuthServiceProvider::class);
$app->register(AbcDaConstrucao\AutenticacaoPackage\Providers\AuthServiceProvider::class);
// ...

Protegendo Rotas

Apesar de apresentar a mesma característica de proteção padrão de rotas, o pacote verifica não só se o token JWT é válido mas também verifica as permissões que o usuário tem na aplicação realizando também a função de ACL.

Exemplo de grupo de rotas protegidas por autenticação.

// Lumen
$router->group(['middleware' => ['auth:sag', 'sag-acl']], function () use ($router) {
    // Routes
});

// Laravel API
Route::middleware(['auth:sag', 'sag-acl'])->group(function () {
    // Routes
});

Autenticação

Método auxiliar para autenticação.

Http::loginRequest($username, $base64_password).

Exemplos em API Lumen

<?php

use AbcDaConstrucao\AutenticacaoPackage\Facades\Http;
use Illuminate\Http\Request;

$router->post('/login', function (Request $request) {
    // A senha deve ser enviada com base64 encode para API de Autenticação. 
    $response = Http::loginRequest($request->username, base64_encode($request->password));

    // Devolve o token caso status 200 ou o erro específico 
    // Ver abaixo resultado esperado do $response.
    return response()->json($response['data'], $response['status']);
});

Exemplo em Frontend Laravel/Lumen

O método Http::loginRequest() também salva o token em sessão caso exista a classe Illuminate\Session\SessionManager, facilitando o manuseio do mesmo e mantendo o usuário logado enquanto o token for válido.

<?php

use AbcDaConstrucao\AutenticacaoPackage\Facades\Http;
use Illuminate\Http\Request;

Route::post('/login', function (Request $request) {
    // A senha deve ser enviada com base64 encode para API de Autenticação.
    $response = Http::loginRequest($request->username, base64_encode($request->password));

    // token obtido e salvo em sessão.
    // Redireciona o usuário a página desejada.
    if ($response['status'] == 200) {
        return redirect()->route('home');
    }

    // Se o token não for emitido retorna o usuário a página de login com os erros.
    return back()->with('errors', $response['data']['message']);
});

Posteriormente poderá acessar o token e passar nas requisições seguintes para o backend da seguinte forma.

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Session;

$token = Session::get(Config::get('sag.session.token'));
$tokenType = Session::get(Config::get('sag.session.token_type'));
$tokenValidate = Session::get(Config::get('sag.session.token_validate'));

Resultado esperado em $response.

// statuscode 200
[
    "status" => 200,
    "data" => [
        "token_tipo" => "Bearer",
        "token_validade" =>  "2022-04-28T17:49:21-03:00",
        "token" => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOlwvXC9zYWctc2VydmVyLmxvY2FsXC9hdXRoXC9sb2dpbiIsImlhdCI6MTY1MTE3NTIzOCwiZXhwIjoxNjUxMTc4ODM4LCJqaXQiOiJqd3RfNSIsInN1YiI6NX0.aSYc0iMkPzVb2EluK7rtXwnkjfv0TdjFFFHuSvd7VMQ",
        "payload" => "eyJpZCI6NSwibmFtZSI6IlRhbGxlcyBHYXplbCIsInVzZXJuYW1lIjoidGFsbGVzIiwiZW1haWwiOiJ0YWxsZXMuZ2F6ZWxAYWJjZGFjb25zdHJ1Y2FvLmNvbS5iciIsImVtYWlsX3ZlcmlmaWVkX2F0IjpudWxsLCJwYXNzd29yZCI6IiQyeSQxMCRnTGF0dk9zcmYwb25PUElodzV1N2hlRTFlTXNZb1Bta3M1cU5EMVdpc0x6bmZnSkdYNHUuZSIsImFjdGl2ZSI6MSwicm9vdCI6MCwiZXhwaXJlIjoxLCJjcmVhdGVkX2J5IjoxLCJ1cGRhdGVkX2J5IjoxLCJyZW1lbWJlcl90b2tlbiI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMjItMDQtMjggMTU6MDI6NDQiLCJ1cGRhdGVkX2F0IjoiMjAyMi0wNC0yOCAxNTowMzowMyIsImFwcHMiOlt7ImlkIjoxLCJuYW1lIjoiYXBpX3BlZGlkb3Nfc2VydmVyLXByb2R1Y3Rpb24iLCJ1cmwiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODAwMSIsImFjdGl2ZSI6MSwic3VwZXJfYWRtaW4iOjAsImNyZWF0ZWRfYnkiOjEsInVwZGF0ZWRfYnkiOjEsImNyZWF0ZWRfYXQiOiIyMDIyLTA0LTI4IDEwOjM4OjU2IiwidXBkYXRlZF9hdCI6IjIwMjItMDQtMjggMTA6Mzg6NTYiLCJncm91cHMiOlt7ImlkIjoyLCJhcHBfaWQiOjEsIm5hbWUiOiJGQVJNIiwiZGVzY3JpcHRpb24iOiJHcnVwbyBkbyBSYW1vbiIsImFjdGl2ZSI6MSwiY3JlYXRlZF9ieSI6MSwidXBkYXRlZF9ieSI6MSwiY3JlYXRlZF9hdCI6IjIwMjItMDQtMjggMTU6MDI6MDciLCJ1cGRhdGVkX2F0IjoiMjAyMi0wNC0yOCAxNTowMjowNyIsInBlcm1pc3Npb25zIjpbeyJhcHBfaWQiOjEsImdyb3VwX2lkIjoyLCJpZCI6NCwibWV0aG9kIjoiUE9TVCIsInVyaSI6IlwvbG9nb3V0IiwibmFtZSI6ImxvZ291dCIsInB1YmxpYyI6MH0seyJhcHBfaWQiOjEsImdyb3VwX2lkIjoyLCJpZCI6MywibWV0aG9kIjoiR0VUIiwidXJpIjoiXC9wcm9maWxlIiwibmFtZSI6InByb2ZpbGUiLCJwdWJsaWMiOjB9XX1dLCJyb3V0ZXMiOlt7ImlkIjoxLCJhcHBfaWQiOjEsIm1ldGhvZCI6IlBPU1QiLCJ1cmkiOiJcL2xvZ2luIiwibmFtZSI6ImxvZ2luIiwicHVibGljIjoxfSx7ImlkIjoyLCJhcHBfaWQiOjEsIm1ldGhvZCI6IkdFVCIsInVyaSI6Ilwvcm90YS1wdWJsaWNhIiwibmFtZSI6InJvdGEtcHVibGljYSIsInB1YmxpYyI6MX0seyJpZCI6MywiYXBwX2lkIjoxLCJtZXRob2QiOiJHRVQiLCJ1cmkiOiJcL3Byb2ZpbGUiLCJuYW1lIjoicHJvZmlsZSIsInB1YmxpYyI6MH0seyJpZCI6NCwiYXBwX2lkIjoxLCJtZXRob2QiOiJQT1NUIiwidXJpIjoiXC9sb2dvdXQiLCJuYW1lIjoibG9nb3V0IiwicHVibGljIjowfSx7ImlkIjo1LCJhcHBfaWQiOjEsIm1ldGhvZCI6IkdFVCIsInVyaSI6IlwvIiwibmFtZSI6ImhvbWUiLCJwdWJsaWMiOjB9XX0seyJpZCI6MywibmFtZSI6ImFwaV9wZWRpZG9zX2Zyb250LXByb2R1Y3Rpb24iLCJ1cmwiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODAwMiIsImFjdGl2ZSI6MSwic3VwZXJfYWRtaW4iOjAsImNyZWF0ZWRfYnkiOjEsInVwZGF0ZWRfYnkiOjEsImNyZWF0ZWRfYXQiOiIyMDIyLTA0LTI4IDE1OjMyOjI5IiwidXBkYXRlZF9hdCI6IjIwMjItMDQtMjggMTU6MzI6MjkiLCJncm91cHMiOlt7ImlkIjozLCJhcHBfaWQiOjMsIm5hbWUiOiJGQVJNIiwiZGVzY3JpcHRpb24iOm51bGwsImFjdGl2ZSI6MSwiY3JlYXRlZF9ieSI6MSwidXBkYXRlZF9ieSI6MSwiY3JlYXRlZF9hdCI6IjIwMjItMDQtMjggMTU6MzM6NDMiLCJ1cGRhdGVkX2F0IjoiMjAyMi0wNC0yOCAxNTozMzo0MyIsInBlcm1pc3Npb25zIjpbeyJhcHBfaWQiOjMsImdyb3VwX2lkIjozLCJpZCI6MTEsIm1ldGhvZCI6IkdFVHxIRUFEIiwidXJpIjoiXC9hcGlcL2hvbWUiLCJuYW1lIjoiYXBpLmhvbWUiLCJwdWJsaWMiOjB9LHsiYXBwX2lkIjozLCJncm91cF9pZCI6MywiaWQiOjksIm1ldGhvZCI6IlBPU1QiLCJ1cmkiOiJcL2FwaVwvbG9nb3V0IiwibmFtZSI6ImFwaS5sb2dvdXQiLCJwdWJsaWMiOjB9LHsiYXBwX2lkIjozLCJncm91cF9pZCI6MywiaWQiOjEwLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvYXBpXC9wcm9maWxlIiwibmFtZSI6ImFwaS5wcm9maWxlIiwicHVibGljIjowfSx7ImFwcF9pZCI6MywiZ3JvdXBfaWQiOjMsImlkIjoxNSwibWV0aG9kIjoiR0VUfEhFQUQiLCJ1cmkiOiJcL2hvbWUiLCJuYW1lIjoiaG9tZSIsInB1YmxpYyI6MH0seyJhcHBfaWQiOjMsImdyb3VwX2lkIjozLCJpZCI6MTYsIm1ldGhvZCI6IkdFVHxIRUFEIiwidXJpIjoiXC9sb2dvdXQiLCJuYW1lIjoibG9nb3V0IiwicHVibGljIjowfV19XSwicm91dGVzIjpbeyJpZCI6NiwiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6Ilwvc2FuY3R1bVwvY3NyZi1jb29raWUiLCJuYW1lIjpudWxsLCJwdWJsaWMiOjF9LHsiaWQiOjcsImFwcF9pZCI6MywibWV0aG9kIjoiR0VUfEhFQUQiLCJ1cmkiOiJcL2FwaVwvcHVibGljYSIsIm5hbWUiOiJhcGkucHVibGljYSIsInB1YmxpYyI6MX0seyJpZCI6OCwiYXBwX2lkIjozLCJtZXRob2QiOiJQT1NUIiwidXJpIjoiXC9hcGlcL2xvZ2luIiwibmFtZSI6ImFwaS5sb2dpbiIsInB1YmxpYyI6MX0seyJpZCI6OSwiYXBwX2lkIjozLCJtZXRob2QiOiJQT1NUIiwidXJpIjoiXC9hcGlcL2xvZ291dCIsIm5hbWUiOiJhcGkubG9nb3V0IiwicHVibGljIjowfSx7ImlkIjoxMCwiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvYXBpXC9wcm9maWxlIiwibmFtZSI6ImFwaS5wcm9maWxlIiwicHVibGljIjowfSx7ImlkIjoxMSwiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvYXBpXC9ob21lIiwibmFtZSI6ImFwaS5ob21lIiwicHVibGljIjowfSx7ImlkIjoxMiwiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvIiwibmFtZSI6InN0YXJ0IiwicHVibGljIjoxfSx7ImlkIjoxMywiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvbG9naW4iLCJuYW1lIjoibG9naW4iLCJwdWJsaWMiOjF9LHsiaWQiOjE0LCJhcHBfaWQiOjMsIm1ldGhvZCI6IlBPU1QiLCJ1cmkiOiJcL2xvZ2luIiwibmFtZSI6InBvc3QubG9naW4iLCJwdWJsaWMiOjF9LHsiaWQiOjE1LCJhcHBfaWQiOjMsIm1ldGhvZCI6IkdFVHxIRUFEIiwidXJpIjoiXC9ob21lIiwibmFtZSI6ImhvbWUiLCJwdWJsaWMiOjB9LHsiaWQiOjE2LCJhcHBfaWQiOjMsIm1ldGhvZCI6IkdFVHxIRUFEIiwidXJpIjoiXC9sb2dvdXQiLCJuYW1lIjoibG9nb3V0IiwicHVibGljIjowfV19XX0="
    ]
]

// statuscode 401
[
  "status" => 401,
  "data" => [
    "message" => "Credenciais inválidas."
  ]
]

// statuscode 401
[
  "status" => 401,
  "data" => [
    "message" => "Usuário desativado."
  ]
]

// statuscode 422
[
  "status" => 422,
  "data" => [
    "message" => "The given data was invalid."
    "errors" => [
        "username" => "O campo username é obrigatório",
        "password" => "O campo password é obrigatório"
    ]
  ]
]

Método de acesso aos dados do usuário.

Após autenticação, os dados do usuário estarão disponíveis na facade Auth do Laravel ou Lumen. Tenham atenção para especificar o guardião caso não seja o default. Exemplos de acesso.

use Illuminate\Support\Facades\Auth;

$user = Auth::guard('sag')->user();

dd($user->toArray());
// result
[
  'id' => 3,
  'name' => 'Nome Sobrenome',
  'username' => 'nome.sobrenome',
  'email' => 'nome.sobrenome@abcdaconstrucao.com.br',
  'email_verified_at' => '2022-02-09 18:04:12',
  'active' => '1',
  'root' => '0',
  'expire' => '1',
  'created_by' => '1',
  'updated_by' => '1',
  'created_at' => '2022-02-09 21:04:12',
  'updated_at' => '2022-02-09 21:04:12',
  'apps' => [
    0 => [
      'id' => 1,
      'name' => 'API_PEDIDOS_SERVER-DEV',
      'url' => 'http://localhost:3000',
      'created_by' => '1',
      'updated_by' => '1',
      'active' => '1',
      'created_at' => '2022-02-09 21:04:13',
      'updated_at' => '2022-02-09 21:04:13',
      'super_admin' => '1',
      'groups' => [
        0 => [
          'id' => 1,
          'app_id' => '1',
          'name' => 'Farming',
          'description' => 'Usuários do grupo Farming API_PEDIDOS_SERVER-DEV',
          'active' => '1',
          'created_by' => '3',
          'updated_by' => '3',
          'created_at' => '2022-02-09 21:04:13',
          'updated_at' => '2022-02-09 21:04:13',
          'permissions' => [
            0 => [
              'id' => 10,
              'app_id' => '1',
              'method' => 'GET',
              'uri' => '/',
              'name' => 'home',
              'public' => '0',
            ],
          ],
        ],
      ],
    ],
    1 => [
      'id' => 2,
      'name' => 'API_PEDIDOS_FRONT-DEV',
      'url' => 'http://localhost:3100',
      'created_by' => '1',
      'updated_by' => '1',
      'active' => '1',
      'created_at' => '2022-02-09 21:04:13',
      'updated_at' => '2022-02-09 21:04:13',
      'super_admin' => '0',
      'groups' => [
        0 => [
          'id' => 2,
          'app_id' => '2',
          'name' => 'Farming',
          'description' => 'Usuários do grupo Farming API_PEDIDOS_FRONT-DEV',
          'active' => '1',
          'created_by' => '3',
          'updated_by' => '3',
          'created_at' => '2022-02-09 21:04:13',
          'updated_at' => '2022-02-09 21:04:13',
          'permissions' => [
            0 => [
              'id' => 4,
              'app_id' => '2',
              'method' => 'GET|HEAD',
              'uri' => '/home',
              'name' => 'home',
              'public' => '0',
            ],
          ],
        ],
      ],
    ],
  ],
]

Dados complementares para o usuário logado.

Caso em sua aplicação seja necessário adicionar mais campos para o usuário logado, basta informar nas configurações uma classe que implemente a interface \AbcDaConstrucao\AutenticacaoPackage\Contracts\MergeLocalUserInterface. Essa classe deve conter o método getUserFromMerge(int $abcUserId) que retorna um array que será mergeado com os dados do usuário logado. Como parâmetro, o método recebe o id do usuário logado para facilitar o relacionamento com os dados locais. Exemplo de implementação.

<?php

namespace App\Repositories;

use AbcDaConstrucao\AutenticacaoPackage\Contracts\MergeLocalUserInterface;

class UserRepository implements MergeLocalUserInterface
{
    /**
     * @param int $abcUserId
     * @return array
     */
    public function getUserFromMerge(int $abcUserId)
    {
        $userLocal = User::getByAbcId($abcUserId);

        return [
            'cpf' => $userLocal->cpf
        ];
    }
}

Abra o arquivo .env e adicione o namespace da classe na chave abaixo.

USER_LOCAL_CLASS=\App\Repositories\UserRepository

Agora ao acessar a facade Auth as chaves adicionais do usuário estarão acessíveis.

[
  'id' => 3,
  'name' => 'Nome Sobrenome',
  'username' => 'nome.sobrenome',
  'email' => 'nome.sobrenome@abcdaconstrucao.com.br',
  'email_verified_at' => '2022-02-09 18:04:12',
  'active' => '1',
  'root' => '0',
  'created_by' => '1',
  'updated_by' => '1',
  'created_at' => '2022-02-09 21:04:12',
  'updated_at' => '2022-02-09 21:04:12',
  'apps' => [...],
  'cpf' => '03614568953' // dado adicionado pela aplicação local
]

Método auxiliar para logout

Http::logoutRequest($tokenTipo, $token); invalida o token no sag em todas as aplicações.
JWT::forgetToken(); remove o token apenas no cache da aplicação atual mas não invalida o token no SAG. As outras aplicações continuarão logadas.

Aplicações API

Logout Global

use AbcDaConstrucao\AutenticacaoPackage\Facades\Http;

$router->post('/logout', ['as' => 'logout', function (Request $request) {
    $header = $request->header('Authorization');
    $token = explode(' ', $header);
    $response = Http::logoutRequest($token[0], $token[1]);
    
    return response()->json($response['data'], $response['status']);
}]);

Para aplicações Frontend Laravel/Lumen

Logout Global

use AbcDaConstrucao\AutenticacaoPackage\Facades\Http;

Route::post('/logout', ['as' => 'logout', function (Request $request) {

    $response = Http::logoutRequest();

    // Redireciona a página desejada.
    if ($response['status'] == 200) {
        return redirect()->route('login');
    }
    
    // Retorna erro caso exista.
    return back()->with('error', $response['data']);
}]);

Resultado esperado em $response.

[
    'status' => 200,
    'data' => [
        'message' => 'Desconectado com sucesso.'
    ]
]

Logout aplicação Local

use AbcDaConstrucao\AutenticacaoPackage\Facades\JWT;

Route::post('/logout', ['as' => 'logout', function (Request $request) {

    JWT::forgetToken();
    
    return redirect()->route('login');
}]);

Método auxiliar para verificar permissão de acesso

O método ACL::hasRouteAccess(string $routeNameOrUri) recebe o nome ou a uri de uma rota e retorna boolean se o usuário atual tem permissão de acesso. Pode ser aplicado em diversas situações de uso.

// Lumen - routes/web.php
$router->get('/api/profile', ['as' => 'api.profile', function (Request $request) use ($router) {
    // ...
}]);

use AbcDaConstrucao\AutenticacaoPackage\Facades\ACL;
// Nome da rota.
ACL::hasRouteAccess('api.profile');

// URI da rota
ACL::hasRouteAccess('/api/profile');

Autorização

Sincronizar rotas da aplicação com a API de Autenticação.

Após criar ou atualizar as rotas da aplicação deve-se usar o command de sincronização.

php artisan abc-sag:sync-routes

De forma opcional pode-se usar um helper em qualquer ponto do código.

use AbcDaConstrucao\AutenticacaoPackage\Facades\ACL;

ACL::syncRoutes();