Open mira-thakkar opened 1 year ago
I found a solution to this problem.
In the documentation it asks to put the default guard as API.
// config/auth.php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
But for some reason when we login using the auth()
helper it does not implicitly look for the default guard previously defined in config/auth.php
For that, I made the guard I want explicit in the Login and Refresh route (in the case of the api
documentation)
For example:
API code
/**
* Get a JWT via given credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
...
/**
* Refresh a token.
*
* @return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
return $this->respondWithToken(auth()->refresh());
}
Code making the guard explicit:
/**
* Get a JWT via given credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth('api')->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
...
/**
* Refresh a token.
*
* @return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
return $this->respondWithToken(auth('api')->refresh());
}
Note that I left the auth('api')
explicit, doing so worked for me with the refresh token.
I found a solution to this problem.
In the documentation it asks to put the default guard as API.
// config/auth.php 'defaults' => [ 'guard' => 'api', 'passwords' => 'users', ], 'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],
But for some reason when we login using the
auth()
helper it does not implicitly look for the default guard previously defined inconfig/auth.php
For that, I made the guard I want explicit in the Login and Refresh route (in the case of the
API
documentation)For example:
API code
/** * Get a JWT via the given credentials. * * @return \Illuminate\Http\JsonResponse */ public function login() { $credentials = request(['email', 'password']); if (! $token = auth()->attempt($credentials)) { return response()->json(['error' => 'Unauthorized'], 401); } return $this->respondWithToken($token); } ... /** * Refresh a token. * * @return \Illuminate\Http\JsonResponse */ public function refresh() { return $this->respondWithToken(auth()->refresh()); }
Code making the guard explicit:
/** * Get a JWT via the given credentials. * * @return \Illuminate\Http\JsonResponse */ public function login() { $credentials = request(['email', 'password']); if (! $token = auth('api')->attempt($credentials)) { return response()->json(['error' => 'Unauthorized'], 401); } return $this->respondWithToken($token); } ... /** * Refresh a token. * * @return \Illuminate\Http\JsonResponse */ public function refresh() { return $this->respondWithToken(auth('api')->refresh()); }
Note that I left the
auth('api')
explicit, doing so worked for me with the refresh token.
The default auth
helper for some reason in Laravel is always simplicity to the web
guard.
Also, It'll be more helpful if we have this written down in the documentation, would you like to contribute to adding this info to the documentation?
@Messhias @zerossB i tried the way you said to specify guard in auth helper. It works well for non-expired token, but it doesn't work on my side for EXPIRED TOKEN. The refresh route just returns from the middleware giving 401 error. I am attaching my code here to make it more understandable
Controller code
public function login(Request $request)
{
$user = User::find(1); // This user comes from google signin, keeping it shorter here to understand
$token = \auth('api')->login($user);
return $this->respondWithToken($token);
}
public function refresh(Request $request)
{
logger()->info('refresh controller',[$request->header('Authorization')]);
$token = \auth('api')->refresh();
return $this->respondWithToken($token);
}
routes/api.php
Route::post('login', [SocialAuthController::class, 'login']);
Route::middleware('auth:api')->group(function () {
Route::post('refresh', [SocialAuthController::class,'refresh']);
});
For the expired token, the log specified in the refresh method is not logged, so it's not that token is failed to refresh, but it doesn't reach upto that line.
I am curious to know if there is something about middleware that i am missing.
The default
auth
helper for some reason in Laravel is always simplicity to theweb
guard.Also, It'll be more helpful if we have this written down in the documentation, would you like to contribute to adding this info to the documentation?
Of course! I can contribute! I'll finalize the help for @mira-thakkar and then summarize for the official documentation.
@Messhias @zerossB i tried the way you said to specify guard in auth helper. It works well for non-expired token, but it doesn't work on my side for EXPIRED TOKEN. The refresh route just returns from the middleware giving 401 error. I am attaching my code here to make it more understandable
Controller code
public function login(Request $request) { $user = User::find(1); // This user comes from google signin, keeping it shorter here to understand $token = \auth('api')->login($user); return $this->respondWithToken($token); } public function refresh(Request $request) { logger()->info('refresh controller',[$request->header('Authorization')]); $token = \auth('api')->refresh(); return $this->respondWithToken($token); }
routes/api.php
Route::post('login', [SocialAuthController::class, 'login']); Route::middleware('auth:api')->group(function () { Route::post('refresh', [SocialAuthController::class,'refresh']); });
For the expired token, the log specified in the refresh method is not logged, so it's not that token is failed to refresh, but it doesn't reach upto that line.
I am curious to know if there is something about middleware that i am missing.
That's strange, I tested it here with the changes you commented on and it worked here. There must be something missing or something like that.
I'm going to send here the files that I changed for you to compare with yours, ok?
// routes/api.php
Route::middleware('api')->prefix('auth')->group(function () {
Route::post('login', 'AuthController@login');
Route::post('logout', 'AuthController@logout');
Route::post('refresh', 'AuthController@refresh');
Route::post('me', 'AuthController@me');
});
// App\Providers\RouteServiceProvider
protected $namespace = "App\\Http\\Controllers";
/**
* Define your route model bindings, pattern filters, and other route configuration.
*/
public function boot(): void
{
$this->configureRateLimiting();
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
});
}
// App\Http\Controllers\AuthController
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Request;
class AuthController extends Controller
{
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login']]);
}
public function login(Request $request)
{
$user = User::find(1);
$token = auth('api')->login($user);
return $this->respondWithToken($token);
}
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60
]);
}
public function me()
{
return response()->json(auth('api')->user());
}
public function logout()
{
auth('api')->logout();
return response()->json(['message' => 'Successfully logged out']);
}
public function refresh(Request $request)
{
$token = auth('api')->refresh();
return $this->respondWithToken($token);
}
}
// config/auth.php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
@Messhias @zerossB i tried the way you said to specify guard in auth helper. It works well for non-expired token, but it doesn't work on my side for EXPIRED TOKEN. The refresh route just returns from the middleware giving 401 error. I am attaching my code here to make it more understandable Controller code
public function login(Request $request) { $user = User::find(1); // This user comes from google signin, keeping it shorter here to understand $token = \auth('api')->login($user); return $this->respondWithToken($token); } public function refresh(Request $request) { logger()->info('refresh controller',[$request->header('Authorization')]); $token = \auth('api')->refresh(); return $this->respondWithToken($token); }
routes/api.php
Route::post('login', [SocialAuthController::class, 'login']); Route::middleware('auth:api')->group(function () { Route::post('refresh', [SocialAuthController::class,'refresh']); });
For the expired token, the log specified in the refresh method is not logged, so it's not that token is failed to refresh, but it doesn't reach upto that line. I am curious to know if there is something about middleware that i am missing.
That's strange, I tested it here with the changes you commented on and it worked here. There must be something missing or something like that.
I'm going to send here the files that I changed for you to compare with yours, ok?
// routes/api.php Route::middleware('api')->prefix('auth')->group(function () { Route::post('login', 'AuthController@login'); Route::post('logout', 'AuthController@logout'); Route::post('refresh', 'AuthController@refresh'); Route::post('me', 'AuthController@me'); });
// App\Providers\RouteServiceProvider protected $namespace = "App\\Http\\Controllers"; /** * Define your route model bindings, pattern filters, and other route configuration. */ public function boot(): void { $this->configureRateLimiting(); $this->routes(function () { Route::middleware('api') ->prefix('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); }); }
// App\Http\Controllers\AuthController <?php namespace App\Http\Controllers; use App\Models\User; use Request; class AuthController extends Controller { public function __construct() { $this->middleware('auth:api', ['except' => ['login']]); } public function login(Request $request) { $user = User::find(1); $token = auth('api')->login($user); return $this->respondWithToken($token); } protected function respondWithToken($token) { return response()->json([ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth('api')->factory()->getTTL() * 60 ]); } public function me() { return response()->json(auth('api')->user()); } public function logout() { auth('api')->logout(); return response()->json(['message' => 'Successfully logged out']); } public function refresh(Request $request) { $token = auth('api')->refresh(); return $this->respondWithToken($token); } }
// config/auth.php 'defaults' => [ 'guard' => 'api', 'passwords' => 'users', ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],
Did it solved your issue?
Sorry, for taking long to get back to this But Unfortunately, no @Messhias
@zerossB i used the exact same code as you specified here, but no luck. also i wonder we're specifying api middleware twice[ RouteServiceProvider & routes/api.php], any specific reason behind that? i want to give details about my jwt configs as well. As of now for testing purpose i use JWT_TTL = 5 (mins) JWT_REFRESH_TTL = 1440 (mins) so when i try to make refresh call with the token after 5 mins, it gives me 401
Sorry, for taking long to get back to this But Unfortunately, no @Messhias
@zerossB i used the exact same code as you specified here, but no luck. also i wonder we're specifying api middleware twice[ RouteServiceProvider & routes/api.php], any specific reason behind that? i want to give details about my jwt configs as well. As of now for testing purpose i use JWT_TTL = 5 (mins) JWT_REFRESH_TTL = 1440 (mins) so when i try to make refresh call with the token after 5 mins, it gives me 401
Ok, waiting for the details.
Experiencing the same issue by reproducing the above code.
Found solution: remove the auth:api
middleware from the refresh
endpoint.
If you put the auth:api middleware on the refresh route, Laravel would try to authenticate the incoming request, and if the token is expired, the middleware would block the request and return an "Unauthenticated" response.
when login, i send back 2 type token, access token (with short TTL) and refresh token (with long TTL). I also add custom claim on access and refresh, so when access token invalid, frontend can send request to refresh token, and they will get new access and refresh token. but this mechanism need to custom by myself. for blacklist token i using this approach https://dev.to/webjose/how-to-invalidate-jwt-tokens-without-collecting-tokens-47pk
Couldn't refresh the token for expired token from refresh api
I am following the exact steps from documentation to implement the Refresh token flow. But
refersh
api returns 401 status for the expired token(Non-expired token works fine), but i suppose refresh_ttl makes sense to refresh expired tokenWhile debugging, I see that it's not going upto controller, but from middleware it gives 401 . I wonder that we're not using any specific middleware for refresh route, so how come package knows that for refresh route, Authenticate middleware will accept expired token, but for other routes, not ?
Your environment:
Steps to reproduce
Implement the package as specified in Quick Start, and call refresh api with expired token
Expected behaviour
refresh api should be able to send new token in exchange of expired token
Actual behaviour
refresh api returns 401 response