So I started using Tymon JWT package for Laravel for my SPA. Its all going good ( adding user, signing in, getting Auth user) but when I make an API request it only works for an hour. I understand that the token I stored on on login expires so when I make a request to the API after 60 minutes it doesnt work. My question is how do I refresh the token and restore it in my local storage? On a new request? Every 59 minutes?
AuthController.php
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' => 'Sorry, we cant find you.']);
}
} 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
$user = JWTAuth::toUser($token);
//Fire off the login event
event( new LoginSuccessful($user) );
return response()->json( compact('token', 'user') );
}
Login.vue
axios.post('/api/authenticate',{
email: this.email,
password: this.password
})
.then(response => {
if(response.data.error){
//Show the error
this.error = response.data.error
this.loading = false;
} else {
this.loading = false;
//Store the items in storage
localStorage.setItem('jwt_token', response.data.token);
localStorage.setItem('user', JSON.stringify(response.data.user));
//Mutate the state
this.$store.commit('login');
//Now go to the dashboard
this.$router.push('dashboard');
}
})
.catch(error => {
});
in my head tag
<script>
window.hopbak = {
'jwt_token': localStorage.getItem('jwt_token'),
'csrfToken': {!! json_encode( csrf_token() ) !!},
};
</script>
in my bootstrap.js
let token = window.hopbak.jwt_token;
if (token) {
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
} else {
console.log('no token');
}
I think you need to register the RefreshToken middleware in app/Http/Kernel.php:
protected $routeMiddleware = [
'jwt.refresh' => 'Tymon\JWTAuth\Middleware\RefreshToken'
];
And then assign it to the routes you want to refresh the token.
Looking at the source code, I can tell that this middleware will handle the refreshing of your token in every request by adding a new token to the response header.
JWT_TTL, JWT_REFRESH_TTL and JWT_BLACKLIST_GRACE_PERIOD values are setted in config/jwt.php file.
How does it work:
The client sends the credentials (email and password) to Laravel and
receives a token (JWT) in response. This token is valid for JWT_TTL
minutes. During this time all requests with the header Authorization
= "Bearer token" will be successful.
For a request made after JWT_TTL minutes, that is, with the token expired, two situations will occur: (1) If there is less than JWT_REFRESH_TTL minutes since the creation of the token (the token carries within it the date of creation on claim IAT), then this token will be invalidated (blacklist) and a new token will be generated and sent as a response to the client. JWT_REFRESH_TTL defines how many minutes after creating the first token the new tokens can be created. For example, for JWT_REFRESH_TTL = 21600, new tokens will be generated for 15 days, after which time the user should reauthenticate. (2) The request occurs after JWT_REFRESH_TTL minutes after the first token was created. In this case, it will not be possible to generate a new token for the client and it must authenticate again. A 401 error will be sent to the client.
When multiple concurrent requests are made with the same JWT,
it is possible that some of them fail, due to token regeneration on
every request. Set grace period in seconds to prevent parallel
request failure, because the JWT will consider to be valid for
JWT_BLACKLIST_GRACE_PERIOD seconds, even if it's on the blacklist.
For more details see this my explanation.
Related
Cookie is avaiable in browser but can not get it even can not check if it exists .
working on laravel and vuejs app where I am using passport for authentication.
I am consuming own api with vuejs so I have added CreateFreshApiToken middleware in kernel.php
vuejs is creating and sending laravel_token cookie successfully but I can not read it using vuejs or javascript.
since I can not check if cookie is available or not I can not check if user is logged in or not.
How I will tell vuejs that user is logged in and take to intended route ?
For now I am saving access_token in localStorage and sending it with each request but I am looking for a solution which can help to use laravel builtin laravel_token cookie to authenticate and authorize the users.
More Details
If i run this command in console then I get response
document.cookie.match('XSRF-TOKEN')
But
document.cookie.match('laravel_token')
returns null
I think the tick icon in the shown image at top is sign of making it private or something that makes it inaccessible.
Update
I found something from internet
But I still want to know how I can tell vuejs app that user is logged in or not ??
Update 2
For now I am sending access_token in header manually from vuejs and it is working fine.
if(localStorage.getItem('laravel_token') !== 'undefined' && !! localStorage.getItem('laravel_token') == true){
window.axios.defaults.headers.common['Authorization'] = 'Bearer '+ localStorage.getItem('laravel_token')
}
A little work around will be for you to implement the authenticated method in your LoginController, and return a json success code. You can then receive this code and store.
public function authenticated(Request $request, $user)
{
// return redirect()->intended($this->redirectPath());
return json_encode(['authenticated' => true]);
}
Using axios...
axios.post('/login', {
email: user.username,
password: user.password
}).then(response => {
.then(response => {
console.log(response)
//response.data.authenticated will return true if user has been authenticated
})
}).catch(err => {
console.log(err)
})
Is it safe to store the access token in a cookie?
I've checked, and even if you have the access token, if you are not properly logged in, you will get a 401.
The access token is changing (I'm changing it) every time the user logs in.
The cookie gets destroyed on sign out or on timeout (based on the rememberme option).
Should I anyway store it somewhere else? Where about?
Yes it's safe, but still you need to add middleware on your routes
Before we are using JWT AUth, this is our solution on frontend side on login page
axios({method: 'post',
data: {email: this.email.trim(),
password: this.password},
url: '/api/login'})
.then(res => {
if (res.data.success) {
// Sets the Cookie Expiration Time to 1 Hour same as JWT Token on the Backend
var now = new Date();
now.setTime(now.getTime() + 1 * 3600 * 1000);
document.cookie = "ut=" + res.data.data.type;
document.cookie = "api_token=" + res.data.data.token;
document.cookie = "expires=" + now.toUTCString() + ";"
// Redirect to a new URL, or do something on success
window.location.href = "/dashboard";
}
}).catch(function (error) {
//error
});
Any suggestions?
I'm not sure if I'm using the correct method for my problem.
I would like to get Infos via Axios. For example: calling: /api/user with Axios gets me the user, but I don't want to see the information when I go on domain.test/api/user.
I even want to use API calls to get Results of Functions even if the User is a Guest.
I installed everything there is on the Laravel Documentation for API, but still I'm not sure how the user gets the Token.
So if I call:
axios.get('/api/user')
.then(response => {
console.log(response.data);
});
I get from the network tab {"message":"Unauthenticated."}. (I didn't forget to set: ...Middleware\CreateFreshApiToken::class and everything there is).
I think my problem is that I didn't register the User correctly. I got my two keys what should I do with it?
And then it's weird, reading the blog https://laravelcode.com/post/laravel-passport-create-rest-api-with-authentication, they use
$success['token'] = $user->createToken('MyApp')->accessToken;
But I don't get it. Where do I save it? I'm super confused, because every blog about shows Laravel Passport completely differently.
Or am I doing it wrong?
Passport is an OAuth2 server implementation which offers several different authorization strategies. While it is certainly possible to use Passport for authentication, it's primary purpose is authorization. This is done via token scopes. The type of token you choose to issue depends on your intended purpose with the API and whom your intended target is to be consuming your api.
Let's start with the following:
$success['token'] = $user->createToken('MyApp')->accessToken;
Here they are creating Personal Access Tokens. This is the simplest means of consuming your api, since clients are able to create the token themselves as needed and are long lived.
The Password Grant strategy is also a good option. A common approach when using this strategy is proxying authentications to Passport internally and merging the client id and secret into the request in a middleware.
public function handle($request, $next) {
return $next(tap($request, function($request) {
$request->merge([
'client_id' => config('services.passport.client.id'),
'client_secret' => config('services.passport.client.secret')
]);
// do any other pre-request work needed here
}));
}
Setup
To get started with Passport, run the command:
php artisan passport:install
This will create the encryption keys as well as a personal access and password grant client types, which will be stored in your database in the oauth_clients table.
Guard and Middleware
API routes need to be using the api guard, done by setting auth:api middleware on the route group(s) or Controller constructor.
// using via constructor
public function __construct()
{
$this->middleware('auth:api');
}
// via a route group
Route::group(['middleware' => ['auth:api'], function() {
// your api routes
});
Passport Routes
Your user model needs to be using the trait HasApiTokens and within the AuthServiceProvider call Passport::routes() in the boot method.
public function boot()
{
$this->registerPolicies();
Passport::routes();
// this is also an ideal time to set some token expiration values
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
}
To use the personal access token strategy for authentication, you need to create your own login and logout routes instead of using the builtin Laravel auth mechanisms.
Referring to the tutorial in your question, they have defined the following routes:
Route::post('login', 'API\PassportController#login');
Route::post('register', 'API\PassportController#register');
and the login method implemented as:
public function login(){
if(Auth::attempt(['email' => request('email'), 'password' => request('password')])){
$user = Auth::user();
$success['token'] = $user->createToken('MyApp')->accessToken;
return response()->json(['success' => $success], $this->successStatus);
}
else{
return response()->json(['error'=>'Unauthorised'], 401);
}
}
This is expecting a ajax request with an email and password to be posted to login and if successful it responds with an access token.
Storing/Using the Token
This token must be stored client side, either in a cookie or local storage, for example, and then added to every request from the client there after. You'll add the token to the Authorization header in client requests.
// Use a response interceptor to check for a token and put it in storage
axios.interceptors.response.use(response => {
// store the token client side (cookie, localStorage, etc)
if (response.data.token) {
localStorage.setItem('token', token)
}
return response
}, error => {
// Do something with response error
return Promise.reject(error);
});
// Use a request interceptor to add the Authorization header
axios.interceptors.request.use(config => {
// Get the token from storage (cookie, localStorage, etc.)
token = localStorage.getItem('token')
config.headers.common['Authorization'] = `Bearer ${token}`
return config;
}, error => {
// Do something with request error
return Promise.reject(error);
});
Accessing the Authenticated User
In order to get the user, pass the 'api' parameter to the auth() helper, Auth facade, or through the request:
$user = auth('api')->user();
$user = Auth::user('api');
$user = request()->user('api');
Keep in mind personal access tokens are long lived, so it's up to you to decide when and how they should expire.
Any http client can be used to make api requests, axios is a very common option and included with each Laravel installation.
I use Larave+JWT and vue2 + vuex2 + axios
So when user logins I store auth token in vuex store. When the token expires I need to refresh it. In order to refresh it I need to send the same token to /refresh route, and get a new token. At least that's how I got it and actually it works.
The problem is that interceptor catches 401 responses and tries to refresh token, but what if, say, in my component I send many requests with expired token? Since ajax requests are async, the interceptor code runs many times. So I got many refresh requests. Once the initial token is refreshed it is not considered valid. When interceptor tries to refresh invalid token server responds with error and I redirect to login page.
Here is the code:
axios.interceptors.response.use((response) => {
return response;
}, (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
axios.post('auth/refresh').then((response) => {
let token = response.data.token
store.dispatch('auth/setAuthToken', token)
let authorizationHeader = `Bearer ${token}`
axios.defaults.headers = { 'Authorization': authorizationHeader }
originalRequest.headers['Authorization'] = authorizationHeader
return axios(originalRequest)
}, (error) => {
store.dispatch('auth/clearAuthInfo')
router.push({ name: 'login' })
})
}
return Promise.reject(error);
});
I think you'll have to change your approach on how you refresh your token. Leaders like Auth0 recommends proactive periodic refresh to solve this problem.
Here is a SO answer where they talk about it.
Set the token expiration to one week and refresh the token every time the user open the web application and every one hour. If a user doesn't open the application for more than a week, they will have to login again and this is acceptable web application UX.
I'm trying to get Soundcloud to return a track ID, but can't get it to work. Documentation here (PHP tab): https://developers.soundcloud.com/docs/api/guide#errors
I've triple checked my credentials from soundcloud that I'm adding as args.
Function in my controller:
function formprocessor()
{
require_once (APPPATH.'third_party/Services/Soundcloud.php');
// create a client object with your app credentials
$client = new Services_Soundcloud('my client id', 'my client secret', 'http://127.0.0.1:8888/index.php/welcome/');
try {
$me = json_decode($client->get('me'), true);
}
catch (Services_Soundcloud_Invalid_Http_Response_Code_Exception $e) {
exit($e->getMessage());
}
}
What would cause this to fail? I keep getting a 401
Here's a reference to the API: https://developers.soundcloud.com/docs/api/reference
you need to authenticate by sending the user (even if thats you) to "authorizeurl":
$authURL = $client->getAuthorizeUrl();
then set the access token with:
$accessToken = $client->accessToken($_GET['code']);
$client->setAccessToken($_SESSION['token']); //if you saved token in a session
Then you should at least be able to "get me" this part is working for me however i cannot access things such as /me/tracks/ or /me/followings/ at the moment.