Laravel Passport "auth:api" middleware acts as "web, auth" middleware - php

I have set up the Laravel Passport package for Laravel 5.3 just as described in the official documentation (https://laravel.com/docs/5.3/passport#introduction).
I want the API to be consumed by a mobile application, so I am trying to implement Password Grant Tokens. I have created a password grant client, and the token request process...
$response = $http->post('http://my-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'my#email.com',
'password' => 'my-password',
'scope' => '',
],
]);
...Just works as expected, returning an access-token and a refresh-token for one of my users.
On the one hand,
php artisan route:list
Lists correct middleware for api/user URI: api,auth:api
And driver for api guard is correctly set to passport in config/auth.php.
Summing up, every step of the installation process has been done (https://laravel.com/docs/5.3/passport#installation).
Defaults contents of api.php:
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:api');
The problem comes when I access to http://my-app.com/api/user, because it seems it is authenticating the request using the 'web' middleware, not the 'api'...
When I access, I am redirected to /login (login form) if the user was not logged in, and to /home if it was...
Any help would be really appreciated.
Thanks in advance.

Solved! Just for the record, the solution:
I was sending the request to http://my-app.com/api/user with HTTP Header wrong. I was sending:
Type: Authorization - Content: Bearer: $accessToken
...and the correct way was:
Type: Authorization - Content: Bearer $accessToken (without colon)
I never thought it could be a typo... Anyway, the error was not easy to detect because the redirection to the login form misleaded me from the beginning. I believe it was such an strange behaviour indeed...

The correct solution is removing redirectTo() from this file Authenticate middleware in app/http/middleware/Authenticate.php

Related

Laravel Passport -> micro service user authentication

For an application I've made a bunch of MicroServices and a Gateway. The gateway receives the request and collects the data from the giroservices. All working fine.
The gateway accepts all the requests and authenticates them using Laravel Passport. So the gateway has Laravel Passport installed.
[gateway]/users/login accepts the login parameters:
[users]/users/verify login details and returns user object. All works fine.
UserController.php
public function login(Request $request){
$rules = [
'email' => 'required|email:rfc',
'password' => 'required|min:8',
];
$user = $this->userService->verifyUser(['email'=> $request->email, 'password'=> $request->password]);
return $this->successResponse(['token'=>$token], Response::HTTP_OK);
}
The $user has the full User json including the UUID. I want to attach that UUID Laravel Passports OAuth. So that when the user authenticates, I can abstract the User UUID and use that for next requests.
{"data":{"uuid":"94b55bed-f084-468a-a2a7-51d38e96aed3","first_name":"John","last_name":"Due","locale":"en","email":"JohnDoe#mail.com","created_at":"2020-12-10T09:40:46.000000Z","updated_at":"2020-12-10T09:40:46.000000Z"}}
Obviously I understand to json_decode this, but how to manually create an access token in Laravel Passport. How would I do that?
You can create an access token using a password grant client_id and client_secret along with the user credentials. You can do this using the /oauth/token route in the service that is holding the passport logic.
Here from the documentation
Creating A Password Grant Client
Before your application can issue tokens via the password grant, you will need to create a password grant client. You may do this using the passport:client Artisan command with the --password option. If you have already run the passport:install command, you do not need to run this command:
php artisan passport:client --password
Requesting Tokens
Once you have created a password grant client, you may request an access token by issuing a POST request to the /oauth/token route with the user's email address and password. Remember, this route is already registered by the Passport::routes method so there is no need to define it manually. If the request is successful, you will receive an access_token and refresh_token in the JSON response from the server:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('http://passport-app.com/oauth/token', [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'taylor#laravel.com',
'password' => 'my-password',
'scope' => '',
]);
return $response->json();
which can be found here.
https://laravel.com/docs/8.x/passport#creating-a-password-grant-client

Laravel Passport Middleware "auth:api" Always Returns 401 with Valid Token

I am creating a Laravel API and want to secure it with Laravel Passport, however, it seems I can't get authenticated.
I began by securing my API routes using the Middleware auth:api inside my RouteServiceProvider.php
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('auth:api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
I also made sure to change the API driver in my config/auth.php to use passport
'api' => [
'driver' => 'passport',
'provider' => 'users',
'hash' => false,
],
I've then generated token using the methods recommended in the Laravel Docs. I did this inside of Postman.
I then used the Bearer token in the request Headers, along with the Accept and Content-Type being set to application/json (I've seen a lot on this issue in which people said this should be done)
And the Route in my api.php
Route::prefix('auth')->name('auth.')->group(function() {
Route::get('/user', function() { return response()->json(['error' => false, 'message' => 'Hello World']); })->name('user');
});
However, when I send the request I received the same 401 error given by Laravel Foundations Handler.php (I've edited the response to fit my use case but that has no affect on my current issue)
"messsage": "Unauthenticated."
Returning with the following headers;
While doing my research I found a question which described what I was experiencing but it seemed that it has been abandoned and no answer was provided. Any ideas?

php process goes unresponsive when making a HTTP POST method call using GuzzleHttp

I am using guzzle HTTP client to issue a password grant based access token after successful user login. I am using passport package for oauth and I have done all the setup which includes the Password Grant Client it creates. In my login controller I override the sendLoginResponse method of the AuthenticatesUsers trait so as to issue an access token on successful email/password authentication
public function sendLoginResponse(Request $request)
{
try {
Log::debug("Auth attempt sucessful, obtaining access_token for user :: ".$request->email);
$client = new Client();
$token_response = $client->post(config('app.url').'/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => config('auth.password_grant.client.id'),
'client_secret' => config('auth.password_grant.client.secret'),
'username' => $request->email,
'password' => $request->password,
'scope' => '*',
],
]);
if($token_response->getStatusCode()!=200) {
Log:error("Login failed to generate Access Token");
throw new InvalidCredentialsException();
}
$request->session()->regenerate();
$this->clearLoginAttempts($request);
$data = json_decode((string) $token_response->getBody(), true);
Cookie::queue('refresh_token',$data->refresh_token,config('auth.tokens.refresh.expire.days')*1440);
Log::debug("Adding Bearer token to Authorization header");
return response()->view('dashboard', [
'expires_in' => $data->expires_in
], 200)->header('Authorization', $data->token_type.' '.$data->access_token);
} catch(Exception $e){
Log::error('Error :: '.$e->getMessage());
throw $e;
}
}
The whole PHP process goes unresponsive when I make this post request, and there are no errors in any of the logs. Exactly at this line
$token_response = $client->post($token_url, .......
I ran this in Debug session; and the URL, Client ID and Secret are generated correctly via configuration properties; the only Exception I could see was a FileNoFoundException that occurs when it does find any cache key for throttle login and that all happens much before this call is made and the app proceeds to authenticate the user.
When I make this very request with same parameters through Postman or via artisan tinker I can get a response with access_token, refresh_token and expires_in data.
A couple of hours with 'Hit And Trial' does really save you 10 minutes of going through 'Documentation'.
Turns out I really don't have to do all this heavy lifting this link shows how we can add the \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class, to web middleware in app/http/Kernel.php which takes care of generating ApiToken for first party apps such as the React JS which I'd be using to consume my own API.
While that solved the intent of writing all this code, I'm still not sure what was causing the process unresponsiveness making the access_token from within the php code.

How to use Laravel Passport with Password Grant Tokens?

I just read the https://laravel.com/docs/5.6/passport documentation and I have some doubts that hopefully someone could help me with:
First, some context, I want to use Passport as a way to provide Oauth authentication for my mobile app (first-party app).
When I use php artisan passport:client --password I get back a Client ID and a Client Secret. Does this value have to be fixed on my app? for example storing them hardcoded or as a "settings" file? If the values shouldn't be stored then how should it work?
To register a user to my app I use: $user->createToken('The-App')->accessToken; I get that the accessToken will be the one used for sending on all my requests as a Header (Authorization => Bearer $accessToken) but what exactly is "The-App" value for?
For login the user I'm using the URL: http://example.com/oauth/token and sending as parameters:
{
"username": "user#email.com",
"password": "userpassword",
"grant_type": "password",
"client_id": 1, // The Client ID that I got from the command (question 1)
"client_secret": "Shhh" // The Client Secret that I got from the command (question 1)
}
When I login the user using the previous endpoint I get back a refresh_token, I read that I could refresh the token through http://example.com/oauth/token/refresh but I try to request the refresh I got Error 419, I removed the url oauth/token/refresh from the csrf verification and now I get back "message": "Unauthenticated.", I'm making the following request:
Content-Type: x-www-form-urlencoded
grant_type: refresh_token
refresh_token: the-refresh-token // The Refresh Token that I got from the command (question 3)
client_id: 1 // The Client ID that I got from the command (question 1)
client_secret: Shhh // The Client Secret that I got from the command (question 1)
scope: ''
Should I use this endpoint? or is not necessary given the app I'm trying to develop.
Finally, there are a lot of endpoints that I get from passport that I don't think I will use for example: oauth/clients*, oauth/personal-access-tokens* is there a way to remove them from the endpoints published by passport?
Thanks a lot for your help!
If you are consuming your own api then you don't need to call http://example.com/oauth/token
for user login because then you need to store client_id and client_secret at app side. Better you create an api for login and there you can check the credentials and generate the personal token.
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
// Authentication passed...
$user = Auth::user();
$token = $user->createToken('Token Name')->accessToken;
return response()->json($token);
}
}
Finally, there are a lot of endpoints that I get from passport that I
don't think I will use for example: oauth/clients*,
oauth/personal-access-tokens* is there a way to remove them from the
endpoints published by passport?
You need to remove Passport::routes(); from AuthServiceProvider and manually put only required passport routes. I think you only need oauth/token route.
what exactly is "The-App" value for?
if you check oauth_access_tokens table it has name field. $user->createToken('Token Name')->accessToken; here the "Token Name" stored in name field.
How to use Laravel Passport with Password Grant Tokens?
To generate password grant token you have to store client_id and client_secret at app side (not recommended, check this ) and suppose if you have to reset the client_secret then the old version app stop working, these are the problems. To generate password grant token you have to call this api like you mention in step 3.
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'taylor#laravel.com',
'password' => 'my-password',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
Generate token from refresh_token
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
You can look this https://laravel.com/docs/5.6/passport#implicit-grant-tokens too.
Tackling Question 5
Finally, there are a lot of endpoints that I get from passport that I don't think I will use for example: oauth/clients*, oauth/personal-access-tokens* is there a way to remove them from the endpoints published by passport?
Passport::routes($callback = null, array $options = []) takes an optional $callback function and optional $options argument.
The callback function takes a $router argument from which you can then choose which routes to install as shown below in your AuthServiceProvider.php that is enabling a more granular configuration:
Passport::routes(function ($router) {
$router->forAccessTokens();
$router->forPersonalAccessTokens();
$router->forTransientTokens();
});
Passport::tokensExpireIn(Carbon::now()->addMinutes(10));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(10));
This way we only create the passport routes that we need.
forAccessTokens(); enable us to create access tokens.
forPersonalAccessTokens(); enable us to create personal tokens although we will not use this in this article. Lastly,
forTransientTokens(); creates the route for refreshing tokens.
If you run php artisan route:list you can see the new endpoints installed by Laravel Passport.
| POST | oauth/token | \Laravel\Passport\Http\Controllers\AccessTokenController#issueToken
| POST | oauth/token/refresh | \Laravel\Passport\Http\Controllers\TransientTokenController#refresh

Get authenticated user with Laravel Passport and grant password

I did an API REST with Laravel and now I'm trying to consume it. The thing is I need to authenticate users in the API and I am using the Password Grant method. I can authenticate users correctly and I can get an access token but from then, I don't see a way to retrieve the authenticated user with the access token in my consuming application.
I tried in the API with a route like this:
Route::get('/user', function(Request $request) {
$user = $request->user();
// Even with
$user = Auth::user();
return $user;
});
No dice. I am reading Passport code but I can't figure it out. My guess is that I would need to specify a new guard type or something because It doesn't seem that Laravel Passport provides one for this kind of grant type...
To clarify things:
I have an API REST application, which is the oAuth2 Server.
I have another application consuming the API REST.
I do know the workflow. In my case, with Password Grant, I get the user credentials in my consumer application, then I make a request to /oauth/token specifying the grant_type to password, I provide the user credentials along with my client credentials, which I am sure they were generated with "php artisan passport:client --password" (note the --password option)
I can get the access token with no problems. What I need now, is to get a JSON representation of the user I just authenticated from the API REST. But here is the problem: I just have an access token. Nothing I can relate with the user.
Or can I? Maybe I can extend the method that authenticates password grant requests to relate the generated access token to the user it is authenticating... *light bulb turns on*
Consuming application test code:
try {
$client = new Client();
$result = $client->post('https://myapi.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => '5',
'client_secret' => 'my_secret',
'username' => 'user_I_am_authenticating',
'password' => 'the_user_password',
'scope' => '',
]
]);
$access_token = json_decode((string) $result->getBody(), true)['access_token'];
$result = $client->get('https://myapi.com/client/user', [
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => "Bearer $access_token",
]
]);
return (string) $result->getBody();
} catch (GuzzleException $e) {
return "Exception!: " . $e->getMessage();
}
Note that https://myapi.com/client/user route is just a route I made for testing in the API. That route is defined as:
Route::get('/user', function(Request $request) {
return $request->user();
});
Now. I know this is not working. This is what I want to achieve. Know the user making the request given the access_token/bearer_token.
You forgot the appropriate middleware.
Route::get('/user', function(Request $request) {
return Auth::user();
})->middleware('auth:api');
The authentication flow is not fired when you don't mention the auth middleware. That's why you get null.
I had the same problem with you. And i solved it after I manually defined the auth guard.
Route::get('/user', function (Request $request) {
return auth()->guard('api')->user();
});
You need to pass the Access token back with every request. Please check the documentation for this part here

Categories