Laravel Passport API Rest

645 views
Skip to first unread message

Fernanda Cipriano

unread,
Mar 12, 2019, 10:56:08 AM3/12/19
to Laravel Brasil
Bom dia galera, tudo bem?

Estou desenvolvendo uma API Rest (lado Server com Laravel) e estou tentando entender qual a melhor maneira de trabalhar com o passport para fazer a autenticação.

Vi que tem como gerar concessões com cliente (php artisan passport:client --client) ou com senha (php artisan passport:client --password) mas confesso que estou perdida em saber qual a melhor maneira de trabalhar.

Sem contar também que quando faço com o --client eu não consigo autenticar. Ele me retorna o token mas quando vou autenticar com esse token dá a mensagem: "message": "Unauthenticated."


A minha dúvida é: qual a melhor maneira de trabalhar quando temos que fazer uma comunicação entre sistemas de linguagens distintas?

Já preparei o client (Cobol) e agora estou na parte do server (Laravel) e não sei qual a melhor maneira de fazer essa autenticação para posteriormente trabalhar com os dados (já autenticado).


Obrigada!
Fernanda Cipriano

Fernanda Cipriano

unread,
Mar 12, 2019, 11:10:48 AM3/12/19
to Laravel Brasil
Gente consegui com o client_credentials, tinha que adicionar na rota também.

Minha dúvida agora á outra, eu já tinha um middleware na rota, como faço para ter 2?

Hoje minha rota é assim:

Route::middleware('auth:api')->prefix('v1')->group(function () {
    Route::get('users/me', function () {
        return \Auth::user();
    });


    Route::resource('materiais', 'MaterialsController')->parameters([ 'materiais' => 'material' ]);

    Route::resources([
        'products' => 'ProductsController',
        'tests' => 'TestController',
        'focas' => 'FocaController',
        'users' => 'UsersController'

    ]);
});


Posso fazer isso?

Route::middleware('auth:api', 'client_credentials')->prefix('v1')->group(function () {
    Route::get('users/me', function () {
        return \Auth::user();
    });


    Route::resource('materiais', 'MaterialsController')->parameters([ 'materiais' => 'material' ]);

    Route::resources([
        'products' => 'ProductsController',
        'tests' => 'TestController',
        'focas' => 'FocaController',
        'users' => 'UsersController'

    ]);
});

Não sei como usar mais de um middleware para a mesma rota e nem sei se isso é uma boa prática.

Obrigada!
Fernanda Cipriano

Clodoaldo Bragato Lopes

unread,
Mar 12, 2019, 11:19:50 AM3/12/19
to Laravel Brasil
Fernanda, pelo que li você pode fazer assim : 


Route::group(['middleware' => ['auth', 'web']], function() {
  // uses 'auth' middleware plus all middleware from $middlewareGroups['web']
  Route::resource('blog','BlogController'); //Make a CRUD controller
});

Fernanda Cipriano

unread,
Mar 12, 2019, 12:34:03 PM3/12/19
to Laravel Brasil
Boa tarde Clodoaldo!

Apliquei isso e realmente funciona, mas só se eu tirar o middleware auth:api.



Route::group([ 'prefix' => 'v1',
  'middleware' => ['auth:api','client_credentials']
], function () {
    Route::get('/orders', function (Request $request) {
        return "Oi";
    });
});

Essa é a rota que gostaria de ter, ela só funciona se tiro o middleware auth:api (estou testando pelo postman) mesmo enviando o token Bearer ele não autoriza. 
Me retorna a seguinte mensagem:  "message": "Unauthenticated."

Alguém sabe me esclarecer o que está acontecendo?

Estou enviado os seguintes parâmetros (para este caso somente no header mesmo): 

"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/x-www-form-urlencoded",
"type": "text"
},
{
"key": "Accept",
"value": "application/json",
"type": "text"
},
{
"key": "Authorization",
"value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImExN2I0MDc3OGRlYjkxMzM3MjNjZTQ1YWU2MzE3YmFhZmRmYTU1M2RiMjliNWQ0ZTFhOGJjOGI3YTY2YWY3Y2IxNDg1ZTIzYzU0M2YyY2UwIn0.eyJhdWQiOiIxIiwianRpIjoiYTE3YjQwNzc4ZGViOTEzMzcyM2NlNDVhZTYzMTdiYWFmZGZhNTUzZGIyOWI1ZDRlMWE4YmM4YjdhNjZhZjdjYjE0ODVlMjNjNTQzZjJjZTAiLCJpYXQiOjE1NTI0MDMwNjMsIm5iZiI6MTU1MjQwMzA2MywiZXhwIjoxNTUyNDg5NDYzLCJzdWIiOiIiLCJzY29wZXMiOltdfQ.NMOTevLg3PMEjjhPzEc4PfFpn5Eeq8DSe_Ha9lmlOm47i7RXNC5Neh0EJlv9fETcjHd7cL5kphvZSH9eZL-dYJt6YU321WJgXTGcDimS10XUB3tferEE4y4b1XeOADTQBBP9D1I8BKvLttecSxDQhbsby9pvbHUzSP52LPF9W--2jIOXONIhq8H2FwyqJQR166iFXc2L3TIjGfaNT4p6ku7kOkJNnRdRvYeGKiqraprj60NfMXzpN_CLMw8ms-8vzcYCJFxYgUDJiSuTSDFuixCN7nzGZwH776JC3A8S55PJGPlt70nQwPcBQekfKJPNMmCOzhGTXvHq2z8xkEbNYs9jHBTyQUiqpZMYS9t17myGIgmM2J5p3dfsm4Q0_QIBksO12ohyNMunozKnNFLvykJ9luRouVmxtr1qmUBsYJ856ssI6XZqZhgiTVrhG_6828WHcJ0qh_Rs_a6cxUsYvRvIwRrFHXj-8uqH0S4siIK4nFNg7A-4kjjzQVsMIGR7OUkOsmEYrlkAgALDJr5FU33L95gJOoO3F2YU1XoF7YGWrb743PnH8ly-zeYN9zKIDOYX9C_D6sAFjie7bI-38uMNR5-xSTPaM2zxsDAj0wyI811bkL6tyqmGt7d6XUW0-w6HGvaDTe9k05J6_y29qqmBXqm_-U7a60Oew3sMuic",
"type": "text"
}
]



Obrigada!

Fernanda Cipriano

unread,
Mar 12, 2019, 12:49:33 PM3/12/19
to Laravel Brasil
Galera, eu fiz testes aqui tirando o auth:api e mandando um token errado, deixando somento o middleware 'client_credentials', ele barrou. Ok!

Agora a pergunta é: nos melhores padrões de desenvolvimento e segurança devo usar o auth:api e o client_credentials ou pode ser somente o client_credentials?

Obrigada!

Rodrigo Pedra Brum

unread,
Mar 12, 2019, 10:34:03 PM3/12/19
to laravel...@googlegroups.com
Fernanda, boa noite.

Em geral eu utilizo somente o 'auth:api' nas rotas que serão manipuladas pelo usuário com um Access Token gerado por um client do tipo password.

Como não sei onde pode estar diferente do que faço, vou descrever como utilizo.

1. Instalar o Passport

Estes passos provavelmente você deve ter executado, pois são exatamente os que estão na documentação, se preferir pode pular para a segunda parte.

composer require laravel/passport

php artisan migrate

php artisan passport:install

Neste comando o Laravel já cria dois clientes um Personal e um Password, vamos utilizar somente o Password que numa instalação como a acima tem o id 2.

Além destes comandos é preciso adicionar as rotas do Passport no método boot de algum Service Provider, Eu normalmente adiciono ao AuthServiceProvider:

public function boot()
{
Passport::routes();

$this->registerPolicies(); // já tem no AuthServiceProvider
}

Também adicione a trait Laravel\Passport\HasApiTokens ao modelo que vai ser autenticado, normalmente o User

E por último altere o arquivo config/auth.php para os seguintes valores:

'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],

// outras guards
],
Este passo é muito importante diz ao Laravel para uitilizar o Laravel Passport para a autenticação das rotas que tiverem com o Middleware auth:api ao invés do TokenGuard que vem por padrão junto com o Laravel.

2. Utilização

Agora tem dois cenários: 
A. O usuário está deslogado e precisa obter um Access Token (Autenticação)
B. O usuário tem um Access Token e quer acessar rotas protegidas (Autorização)

Vamos  ver os dois cenários:

2-A. Autenticação

Neste caso, para um usuário que existe na base, fazemos uma requisição POST para o endpoint /oauth/token que já é instalado pelo Laravel Passport:

POST http://example.com/oauth/token
Content-Type: application/json
Accept: application/json, text/plain, */*

{
"client_id": 2,
"client_secret": "secret",
"grant_type": "password",
"username": "usu...@example.com",
"password": "123456",
"scopes": "[*]"
}

Note que usamos o client_id e o client_secret do cliente do tipo password, neste exemplo usei o client_id = 2 que numa instalação padrão é um cliente do tipo Password, mas você pode utilizar quaisquer clientes do tupo Password.

A informaçào sobre os escopos autorizados fica armazenada no Access Token, que é um JWT (uma representação encriptada de alguns dados do usuário e dos escopos autorizados). Pode estar aí o problema na sua implementação.

Esta requisição vai retornar um payload parecido com o abaixo:

{
    "token_type": "Bearer",
    "expires_in": 1296000,
    "access_token": "token123",
    "refresh_token": "token456"
}

O que você precisa salvar é o Access Token, para utilizar nas rotas com autenticação. O Refresh Token é útil quando queremos renovar um Access Token expirado, mas exige um outro fluxo e por simplicidade não vou abordar tal fluxo.


2-B. Autorização

Basta utilizarmos o Access Token obtido na autenticação, para acessar rotas protegidas com pelo Guard de api.

Por exemplo, imagina que tenhamos a seguinte rota:

Route::get( 'api/user', function ( \Illuminate\Http\Request $request ) {
return $request->user();
} )->middleware( 'auth:api' );

Bastaria fazermos a seguinte requisição:

GET http://example.com/api/user
Content-Type: application/json
Accept: application/json, text/plain, */*
Authorization: Bearer token123

Utilizanto o mesmo Access Token obtido na autenticação. Note que não usamos aspas e o prefixo Bearer é separado por um espaço apenas do token.

Note que não foi necessário nesta rota enviarmos nem o client_id nem o client_secret.

Daí fica a pergunta: E para que serve o grant do tipo client?

De acordo com a documentação o grant do tipo client é adequado para comunicações máquina-máquina. 

Por exemplo: imagine que, por requisito funcional, seu sistema cliente (o feito em Cobol) precise ter uma rotina agendada para enviar uma notificação a cada usuário pela manhã com as tarefas pendentes. Eu normalmente implementaria do lado do servidor, mas vamos imaginar isto como exemplo.

Como os dados estão na aplicação servidor (a feita em Laravel) esta rotina do sistema cliente vai precisar interagir de alguma forma com a aplicação servidor para manipular estes dados e provavelmente acessar rotas onde você vai querer ter algum nível de autenticação. Mas nesta comunicação o sistema cliente não vai estar executando com nenhum usuário logado, ele vai estar executando sozinho. Como garantir esta autenticação?

Daí que entra o grant do tipo client. 

Primeiro você cria um client do tipo client com o comando que você citou no seu e-mail:

php artisan passport:client --client

Este comando vai fornecer um client_id e um client_secret deste tipo que tem este grant autorizado.

De posse destes dados, você faz uma requisição para o /oauth/token para obter um Access Token para ser utilizado na comunicação máquina-máquina:

POST http://example.com/oauth/token
Content-Type: application/json
Accept: application/json, text/plain, */*

{
"client_id": 3,
"client_secret": "secret",
"grant_type": "client_credentials",
"scopes": "[*]"
}

Assim como no exemplo de autenticação em 2-A, esta rota vai te retornar um Access Token (mas não vai retornar um Refresh Token).

Com este Access Token, você pode acessar rotas que estão protegidas pelo middleware Laravel\Passport\Http\Middleware\CheckClientCredentials.

Por exemplo, assuma que você tem a seguinte rota:

Route::get( '/api/send-notifications', function () {
return 'sent!';
} )->middleware( \Laravel\Passport\Http\Middleware\CheckClientCredentials::class );


Esta rota não precisa de um usuário especifico, mas requer autenticação. Com o Access Token obtido no grant anterior podemos acessá-la mesmo não representando um usuário específico, por exemplo:
GET http://example.com/api/send-notifications
Content-Type: application/json
Accept: application/json, text/plain, */*
Authorization: Bearer client-token123

Num projeto que estou trabalhando eu utilizo este tipo de rota para realizar o registro de novos usuários, já que um dos requisitos é usuário poder se registrar pela aplicação cliente. 
Nesta rota eu não tenho como ter um Access Token de usuário, pois o usuário ainda não existe. E também como a API não é pública eu preciso de alguma forma de autenticação, então utilizo esta estrategia.

Respondendo a sua última pergunta: 
1. Utilize o auth:api quando você precisa autenticar um usuário. 
2. Utilize o middleware CheckClientCredentials quando você precisa autenticar uma máquina. 

Você deve estar se perguntando: Mas eu utilizei somente o middleware CheckClientCredentials com um Access Token de usuário e funcionou, e quando uso o auth:api não funciona (que foi o que você descreveu).
Realmente o middleware CheckClientCredentials funciona com um Access Token fornecido em nome de um usuário, mas o contrário não ocorre, ou seja numa rota com o middleware auth:api um Access Token de máquina não vai ser autorizado.
Além disto a implementação interna muda um pouco, quando você utilizar o middelware auth:api o usuário é adicionado ao Request durante a autenticação, assim quando você precisar acessar o usuário via $request->user() o usuário
já existe no request e o Laravel não precisa ir ao banco de dados para recuperar os dados de usuário.
Quando você utiliza o middleware CheckClientCredentials com um Access Token de usuário e acessa $request->user(), o Laravel vai revalidar o token acessando as tabelas oauth novamente. Ou seja, acessa as tabelas de oautth 2 vezes.
Num teste que fiz ativando o query logger, usando um Access Token de usuário numa rota com o middleware auth:api o Laravel executa 4 consultas ao banco. Já utilizando o CheckClientCredentials o Laravel executa 5 consultas ao banco.
A consulta a mais é a revalidação do token do usuário. Utilizando os dois middlewares, como você tentou antes, também executa 5 requisições.
O fato do middleware auth:api não ter funcionado na sua implementação pode ter sido algum detalhe que você deixou passar, não vi a sua implementação, mas provavelmente você não alterou o aquivo config/auth.php para que o guard api utilize o Passport.

Espero que ajude,
- Rodrigo

Referências:





--
Você recebeu essa mensagem porque está inscrito no grupo "Laravel Brasil" dos Grupos do Google.
Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para laravel-brasi...@googlegroups.com.
Para mais opções, acesse https://groups.google.com/d/optout.


--
Rodrigo Pedra Brum
rodrig...@gmail.com

Fernanda Cipriano

unread,
Mar 13, 2019, 7:10:00 AM3/13/19
to laravel...@googlegroups.com
Bom dia Rodrigo!

Tudo bem?

Entendi perfeitamente tudo! Clareou muitas dúvidas por aqui!

Meu cenário é exatamente esse, será uma "ponte" de dados, assim que cadastrar um registro no Cobol, será chamado o WS para gravar no lado do Laravel.
Logo penso que não preciso de um usuário para isso, pois será o sistema Cobol que está fazendo o envio desses dados para o Laravel. 

Claro que no sistema Cobol teremos um usuário fazendo essa ação, mas no Cobol já terei essa informação de quem está inserindo/alterando os dados, então só levarei para o Laravel os demais dados.

Se entendi bem, implementarei o client_credentials, pois não vejo necessidade de trabalhar com um usuário.

O problema que encontrei (me esclareça se puder por favor) é que não foi possível utilizar os dois middleware's na mesma rota (auth;api e o client_credentials), mas pelo o que entendi, quando utilizo o client_credentials realmente não há necessidade de utilizar o auth:api, é isso mesmo?

O auth:api funcionou aqui, desde que esteja somente ele na rota, sem o client_credentials.
Eu já havia configurado o guard api para o passport, mesmo assim eles dois não funcionaram juntos.

Muito obrigada mais uma vez!


Rodrigo Pedra Brum

unread,
Mar 13, 2019, 7:16:29 AM3/13/19
to laravel...@googlegroups.com
Fernanda, bom dia.

Que bom que o e-mail ajudou de alguma forma.

Respondendo a sua pergunta: não é necessário utilizar os dois middlewares. Basta um.

Inclusive, coml você disse que seu caso de uso o Laravel não precisa ter um usuário explícito eu usaria o client credentias.

At.te,

- Rodrigo Pedra Brum

Fernanda Cipriano

unread,
Mar 13, 2019, 7:25:24 AM3/13/19
to laravel...@googlegroups.com
Obrigada mais uma vez!

Ótimo dia à todos!

Reply all
Reply to author
Forward
0 new messages