I'm doing my authentication system with Laravel & JWT, but I have some questions.
I use the tymon jwt package
I have a token generated at login, for 24 hours, and if the remember-me box is checked, it is valid for 2 years.
Except that how should I proceed to renew the token during these 2 years, I guess I don't have to keep the same one, for security reasons?
Do I have to store something in a database ? like a remember-me token for example, or a refresh-token ?
I'm a bit lost with all this, and I'd like to understand how to proceed.
I've already searched quite a bit on the internet, but I can't find what I want, or it's incomplete.
public function login()
{
$credentials = request(['email', 'password']);
$ttl = env('JWT_TTL');
if (request(['remember_me']) == true) {
$ttl = env('JWT_REMEMBER_TTL');
}
if (!$token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Wrong credentials'], 401);
}
return $this->respondWithToken($token, $ttl);
}
protected function respondWithToken($token, $ttl)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => $ttl
]);
}
JWT_TTL=1440
JWT_REMEMBER_TTL=1051200
Thanks in advance,
You don't have to store anything in a database.
Create two JWTs, one as the access token (login) and one for remember me. Set the access token to expire for 24 hours as usual, and the remember me token to expire for 2 years.
On your protected route, check if the access token is expired, and if it is, check for the remember me token. If the remember me token is present, issue a new access token.
You can handle it in this way as well and its working fine with JWT package using setTTL function and define two different time in .env file.
$ttl = ($request->remember_me === true) ? env('JWT_REMEMBER_TTL') : env('JWT_TTL'); if (! $token = auth()->setTTL($ttl)->attempt($credentials)) {}
Related
I have an api that has a method to start and I am calling it from a frontend project.
In the front end project I use Guzzle to make the call via post to the api and login, from which I get back a json with the user data and a jwt token.
But when I receive the token as I manage the session, I must create a session and save the token, since the laravel to authenticate I need a model user and have a database, which of course I do not have in this backend because I call the api to log in, which brings a token and user data, then as I manage it from the backend, I'm a little lost there.
$api = new Api();
$response = $api->loginapi(['user'=>'wings#test.com','password'=>'123']);
Because here I could not do Auth::login($user) to generate the session.
Because I don't have here the database because the login is done from the api.
There I call the api, of which the answer is the token, but how do I manage it from here, creating a session? saving the token?
thanks for your help.
With api, you don't usually manage a session. usually, you'd call something like
Auth::attempt([
'email' => 'me#example.com',
'password' => 'myPassword'
]);
If the credentials are correct, laravel will include a Set-Cookie header in response, and, that is how you authenticate with api. Via an auth cookie. You don't need to do anything else.
Let's show you how:
//AuthController.php
public function login(Request $request) {
$validatedData = $request->validate([
'email' => 'required|email',
'password' => 'required'
]);
if(Auth::attempt($validatedData)){
return ['success' => 'true'];
}
else{
return ['success' => false, 'message' => 'Email or password Invalid'];
}
}
public function currentUser (){
return Auth::user();
}
Now, the APi file
Route::post('/login', ['App\Http\Controllers\AuthController', 'login']);
Route::get('/current_user', ['App\Http\Controllers\AuthController', 'currentUser']);
Now if you make a call to /api/current_user initially, you'll get null response since you're not currently logged in. But once you make request to /api/login and you get a successful response, you are now logged in. Now if you go to /api/current_user, you should see that you're already logged in.
Important ::
If you are using fetch, you need to include credentials if you're using something other than fetch, check out how to use credentials with that library or api
You want to use the API to authenticate and then use the SessionGuard to create session including the remember_me handling.
This is the default login controller endpoint for logging in. You don't want to change this, as it makes sure that user's do not have endless login attempts (protects for brut-force attacks) and redirects to your current location.
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
if ($request->hasSession()) {
$request->session()->put('auth.password_confirmed_at', time());
}
return $this->sendLoginResponse($request);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
The core happens when we try to "attemptLogin" at
protected function attemptLogin(Request $request)
{
return $this->guard()->attempt(
$this->credentials($request), $request->boolean('remember')
);
}
When using the SessioGurad (which is default) the method attemptLogin fires a couple of events, checks if the user has valid credentials (by hashing the password and matching it with db) and then logs the user in, including the remember me functionality.
Now, if you don't care about events, you can just check from your API if the credentials match and then use the login method from the guard. This will also handle the remember me functionality. Something like this:
protected function attemptLogin(Request $request)
{
$username = $request->input($this->username());
$password = $request->input('password');
$result = \Illuminate\Support\Facades\Http::post(env('YOUR_API_DOMAIN') . '/api/v0/login' , [
'username' => $username,
'password' => $password
])->json();
if(empty($result['success'])){
return false;
}
// Maybe you need to create the user here if the login is for the first time?
$user = User::where('username', '=', $username)->first();
$this->guard()->login(
$user, $request->boolean('remember')
);
return true;
}
I have an API with Laravel 8 and use JWT for authentication
When I log in several times, a new token is created for the user, but the problem is that the previously created tokens are valid and usable. (Until the token expires)
So how can I fix this bug?
this is my login() and createNewToken() method in AuthController() :
public function login(Request $request) {
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required|string|min:6',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
if (! $token = JWTAuth::attempt($validator->validated())) {
return response()->json(['error' => 'احراز نشده'], 401);
}
return $this->createNewToken($token);
}
protected function createNewToken($token) {
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => JWTAuth::factory()->getTTL() * 240,
'user' => auth()->user()
]);
}
I think the best way is to check in login method , if this user has a token, invalidate it and create a new one, else create a new token.
but I don't know how to check the user token with JWT
In advance thanks for your help.
You can check for sessions in the login method.
For example, you can create unique IDs for sessions and set them as jti claims for each token you issue (see here). You need to store them somewhere (e.g. a cache data store). Then, during login, you check if the jti of the received JWT is valid. This way, you can check if a token is revoked or not.
But keep in mind that this undermines the stateless nature of the JTI.
A better approach when using JWTs is to have short lived access tokens (e.g. 5-15 minutes) and use refresh tokens to get new tokens, so that the user doesn't have to log in every 5 minutes. In such a scenario you should not be bothered by an old token - it will only be valid for a few minutes anyway.
If you really need an option to "revoke" access tokens, you should implement it in the part which validates received JWTs, not the login method. As #Deniz suggested in his answer you will need a store to keep some data which can be validated with the content of the JWT. Using session ID in the jti claim is one valid option. Another option would be to keep in a database a timestamp of the last issued access token for a given user, and then validate the iat claim - issued at. If you receive a token for a given user with iat lower than what you have in the DB, you reject the request.
Anyway, if you want to revoke a JWT only because someone logged in again, then it sounds like you want to use sessions, and JWTs are not good for sessions. Just go with plain old cookies.
I make #vue/cli 4.0.5 / vuex 3 app with data reading from Laravel 6 Backend REST API app with passport
as auth and I want to set bigger time of logged session during development and for this in my
app/Http/Controllers/AuthController.php I changed time and session
protected function respondWithToken($token)
{
$loggedUser = $this->guard()->user();
$user_avatar_path = User::getUserAvatarPath($loggedUser->id, $loggedUser->avatar);
$filenameData = User::setUserAvatarProps($loggedUser->id, $loggedUser->avatar, true);
return response()->json([
'access_token' => $token,
'user' => $loggedUser,
'token_type' => 'bearer',
'user_avatar_path' => $user_avatar_path,
'filenameData' => $filenameData,
'expires_in' => $this->guard('api')->factory()->getTTL() * 660
]);
}
But seems this chages does not influence my app. Did I miss some options?
2) Also as I can see parameter expires_in starts its time from login time and that seems to me not what I want,
as I want this parameter works as time from last work in the app.
In other way any request to backend part I have to refresh this this parameter...
MODIFIED :
I read in “Passport Token Lifetimes” :
By default, Passport issues long-lived access tokens that expire after
one year
Looks like that not the issue, as I have default options(one year).
Are the some options on vuejs side, when I use vue-resource
Here https://github.com/pagekit/vue-resource/blob/master/docs/api.md
I see that vue-resource has timeout (number) option, but I am not sure how it is applicable?
I login request :
Vue.http.post(apiUrl + '/auth/login', userCredentials).then(response => {
...
?
MODIFIED #2:
I have in backend app:
"php": "^7.2",
"barryvdh/laravel-cors": "^0.11.4",
"laravel/framework": "^6.2",
"laravel/passport": "^8.1",
And on Vue/Cli part :
"store": "^2.0.12",
"vue": "^2.6.10",
"vue-js-modal": "^1.3.31",
"vue-resource": "^1.5.1",
"vue-router": "^3.1.3",
"vuex": "^3.1.2"
Actualy I want in backend app in config/app.php to set some parameter like
'personal_access_tokens_expire_in_hours' => 24, // Actually I think about value = 1
and to user it in 2 places :
in app/Providers/AuthServiceProvider.php :
public function boot()
{
$this->registerPolicies();
Passport::routes();
$personal_access_tokens_expire_in_hours = config('app.personal_access_tokens_expire_in_hours',24);
Passport::personalAccessTokensExpireIn(Carbon::now()->addHours($personal_access_tokens_expire_in_hours));
}
and in app/Http/Controllers/AuthController.php :
public function login(Request $request)
{
$credentials = request(['email', 'password']);
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
'remember_me' => 'boolean'
]);
if ( ! Auth::attempt($credentials)) {
return response()->json(['message' => 'Unauthorized'], 401);
}
$user = $request->user();
$user->last_logged= Carbon::now(config('app.timezone'));
$user->save();
$tokenResult = $user->createToken('Access Token');
$token = $tokenResult->token;
if ($request->remember_me) {
$personal_access_tokens_expire_in_hours = config('app.personal_access_tokens_expire_in_hours',24);
$token->expires_at = Carbon::now()->addHours($personal_access_tokens_expire_in_hours);
} // Though Ronak Dhoot wrote that $token->expires_at does not infleunce anything.
I added last_logged field to users and fill it on any login.
With default personalAccessTokensExpireIn value in 1 day I login in the system in the middle of my working day.
I turn off computer in the end of the day and opening it next morning I can enter my app with login I made
yesterday(24 hours has not passed yet). That seems not safe for me.
I would prefer personalAccessTokensExpireIn = 1 hour and refresh it ANY authorized request from my vue/cli app.
In which way that could be done? Working on vue/cli apps with baxkend api which way do you use?
I have some prior work with auth/jwt and in app/Http/Controllers/API/AuthController.php I found methods :
public function refresh() // THIS METHOD IS NOT CALLED ANYWHERE
{
return $this->respondWithToken($this->guard()->refresh());
}
protected function respondWithToken($token)
{
$loggedUser= $this->guard()->user();
$user_avatar_path= User::getUserAvatarPath($loggedUser->id, $loggedUser->avatar);
$filenameData = User::setUserAvatarProps($loggedUser->id, $loggedUser->avatar, true);
$usersGroups= User::getUsersGroupsByUserId($loggedUser->id, false);
return response()->json([
'access_token' => $token,
'user' => $loggedUser,
'token_type' => 'bearer',
'user_avatar_path' => $user_avatar_path,
'filenameData' => $filenameData,
'usersGroups' => $usersGroups,
'expires_in' => $this->guard('api')->factory()->getTTL() * 9360 // TOFIX
]);
}
Can refresh() be used in my passport issue somehow?
Thanks!
The createToken() method creates a Personal Access Token. By default, these tokens expire after 1 year (or 100 years, if created by laravel/passport <= 1.0.11). The expiration time for this type of token is not modified by the Passport::tokensExpireIn() or Passport::refreshTokensExpireIn() methods.
laravel/passport >= 7.0.4
Passport version 7.0.4 added a new method Passport::personalAccessTokensExpireIn() that allows you to update the expiration time for personal access tokens. If you are on this version or later, you can add this method call to your AuthServiceProvider::boot() method.
Passport::personalAccessTokensExpireIn(Carbon::now()->addDays(1));
laravel/passport < 7.0.4
If you are not yet on passport version 7.0.4, you can still modify the personal access token expiration time, but it is more manual. You will need to enable a new instance of the personal access grant with your desired expiration time. This can also be done in your AuthServiceProvider::boot() method.
$server = $this->app->make(\League\OAuth2\Server\AuthorizationServer::class);
$server->enableGrantType(new \Laravel\Passport\Bridge\PersonalAccessGrant(), new \DateInterval('P100Y'));
Note:
Modifying the expires_at field in the database will not do anything. The real expiration date is stored inside the token itself. Also, attempting to modify the exp claim inside the JWT token will not work, since the token is signed and any modification to it will invalidate it. So, all your existing tokens will have their original expiration times, and there is no way to change that. If needed, you will need to regenerate new tokens.
PS:
These methods should be called from the boot method of your AuthServiceProvider.
I'm trying to change the expiration date of access token Laravel Passport.
Here's what I have tried:
AuthServiceProvider
public function boot(){
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(Carbon::now()->addDays(1));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(2));
Passport::personalAccessTokensExpireIn(Carbon::now()->addMonths(1));
}
UserController
public function login() {
$credentials = [
'email' => request('email'),
'password' => request('password')
];
if (Auth::attempt($credentials)) {
$success['token'] = Auth::user()->createToken('MyApp')->accessToken;
$success['name'] = Auth::user()->name;
return response()->json(['success' => $success]);
}
return response()->json(['error' => 'Unauthorized'], 401);
}
But it didn't work. The expired date didn't change in the database at field expires_at, it's still one year by default.
I'm trying to do this, cause I want to make a redirect to login form when access token will get expired. How can I do it?
I'm also not sure what would happen with a refresh token, will it return another access token and the user will not need to authorizе?
You're creating a personal access token that belongs to user.
A personal access token has a default expiration date of 1 year.
Looking at your code I'm pretty sure that this command should do the work:
Passport::personalAccessTokensExpireIn(Carbon::now()->addMonths(1));
Double-check the expire_at column in the database and expires_in value in your response when you getting the token. It shows the number of seconds the token lives.
Authentication is working, I have a few routes under auth middleware, Whenever i request it throws :
{
"message": "Failed to authenticate because of bad credentials or an invalid authorization header.",
"status_code": 401
}
How can i send the token with the request like :
Authorization bearer {{Long token}}
It works with `postman`, How can i send the token with request header, Or in any other best way.
Route :
$api->get('/categories', [
'uses' => 'App\Http\Controllers\CategoryController#index',
'as' => 'api.categories',
]);
Method :
public function index() {
$lessons = \App\Category::all();
$token = JWTAuth::getToken(); // $token have jwt token
return response()->json([
'data' => $lessons,
'code' => 200,
]);
}
The question was pretty vague to answer. Please be more specific from next time. From your comments i could finally realise that you want to consume the api from a mobile app.
You need to return the token generated for an user either during login or during registration or any other authentication method/route you have. The mobile app needs to read this response and store the token locally. Then the app needs to inject this token in the request header for every single request. That's the normal api token workflow.
The app should also be coded to read the error response from requests and if it returns errors for expired or invalid token, the app needs to clear the locally stored token and then request the user to login again to generate a fresh token.
you can use : https://github.com/tymondesigns/jwt-auth
requriment :
Laravel 4 or 5 (see compatibility table)
PHP 5.4 +
Steps:
1 : add below line in composer.json in require array
"tymon/jwt-auth": "0.5.*"
2 : run "composer update" in your terminal
3 : after this you have to register service provider
go to config/app.php
and add 'Tymon\JWTAuth\Providers\JWTAuthServiceProvider' this in provider array
and 'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth' , 'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory' this to aliases array
4 : publish pacakge :
"php artisan vendor:publis --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
5 : generate secrate key in config file
'php artisan jwt:generate'
6 : for addition configuration : https://github.com/tymondesigns/jwt-auth/wiki/Configuration
Usage :
AuthenticateController.php
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
class AuthenticateController extends Controller
{
public function authenticate(Request $request)
{
// grab credentials from the request
$credentials = $request->only('email', 'password');
try {
// attempt to verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['error' => 'could_not_create_token'], 500);
}
// all good so return the token
return response()->json(compact('token'));
}
}
You can also skip user authentication and just pass in a User object. e.g.
// grab some user
$user = User::first();
$token = JWTAuth::fromUser($user);
The above two methods also have a second parameter where you can pass an array of custom claims. e.g.
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
JWTAuth::attempt($credentials, $customClaims);
// or
JWTAuth::fromUser($user, $customClaims);
create token based on anything
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload);
d