Say I want to create an AuthService in vue-router to validate the current session before proceeding to the next route, like the example on:
http://vuejs.github.io/vue-router/en/api/before-each.html
router.beforeEach(function ({ to, next }) {
if (to.path === '/auth-required') {
// return a Promise that resolves to true or false
return AuthService.isLoggedIn()
} else {
next()
}
})
How would one approach this for Laravel 5.1 use without using JSON Web Tokens(JWT)?
Is there a "best-pratice" approach to SESSION_DRIVER in Laravel, ex. Redis, for this scenario?
I've searched around the web alot but never seen any attempt do authenticate a session with vue-router without JWT.
For your code, you want to move the isLoggedIn() auth check into the if statement. Your auth service should return a boolean if the user is logged in. Within the if, you would route the user to the appropriate path. beforeEach works like "What should we do before each route is processed?" so you do not need to return a truthy value inside the if statement.
router.beforeEach(function (transition) {
if (transition.to.auth && !AuthService.isLoggedIn()) {
// if route requires auth and user isn't authenticated
transition.redirect('/login')
} else {
transition.next()
}
})
If you want to "validate the current session before proceeding to the next route" each time, your isLoggedIn() would need to call your login API each time. It's usually not best practice because once you login, why do you need to check again? That's why tokens and JWT exist. After you login, you're given a token, you remember this token and send the token in upcoming requests.
How would one approach this for Laravel 5.1 use without using JSON Web
Tokens(JWT)?
Not technically JWTs, you can use API tokens. API tokens can be generated with Laravel's str_random() function. You'd associate 1 token per user and keep the tokens unique. You can put this token in 2 places: 1. in the URL for parameter ?api_token=XXX 2. in the header for "Authorization: Bearer XXX".
If you're going with headers, in Vue.js, you'd setup vue-resource as such:
Vue.http.headers.common['Authorization'] = 'Bearer ' + token;
and then all your requests now contain the API token.
Is there a "best-pratice" approach to SESSION_DRIVER in Laravel, ex.
Redis, for this scenario?
Not 100% sure what you mean here, but tokens are considered one of the best practices when interacting with APIs. You exchange the token with each web request so that you do not need to send a username/password each time.
Related
TLDR; see image below 3 - is that possible and how?
I read about API protection - Sanctum & Passport, but none of these seems what I can accomplish with my app since it's a little specific and simplified in a way.
For example, Sanctum's way of authenticating sounds like something I'd like, but without the /login part (i have a custom /auth part, see below.): https://laravel.com/docs/8.x/sanctum#spa-authenticating.
If the login request is successful, you will be authenticated and
subsequent requests to your API routes will automatically be
authenticated via the session cookie that the Laravel backend issued
to your client.
My app has no login per se - we log-in the user if they have a specified cookie token verified by the 3rd party API (i know token-auth is not the best way to go, but it is quite a specific application/use). It's on /auth, so Sanctum's description above could work, I guess if I knew where to fiddle with it. Our logic:
VueJS: a mobile device sends an encrypted cookie token - app reads it in JS, sends it to my Laravel API for verification.
Get the token in Laravel API, decrypt, send to 2nd API (not in my control), verifying the token, and sends back an OK or NOT OK response with some data.
If the response was OK, the user is "logged-in."
The user can navigate the app, and additional API responses occur - how do I verify it's him and not an imposter or some1 accessing the API directly in the browser?
I guess the session could work for that, but it's my 1st time using Laravel, and nothing seemed to work as expected. Also, sessions stored in files or DB are not something I'm looking forward to if required.
For example, I tried setting a simple session parameter when step 3 above happened and sending it back, but the session store was not set up, yet it seemed at that point. Then I could check that session value to make sure he's the same user that was just verified.
For an easier understanding of what I'm trying to accomplish and if it's even feasible:
The main question is, what is the easiest way to have basic API protection/authentication/verification whilst sending the token for authentication to 3rd party API only on 1st request (and if the app is reopened/refreshed of course) - keeping in mind, that no actual users exist on my Laravel API.
Or would it be best to do the token-auth to the 3rd party API on each request?
If I understand your case correctly there's no real User model involved, right? If so, you'll not be able to use any of Laravel's built-in authentication methods as they all rely on the existence of such a model.
In that case you'll need one endpoint and a custom authentication Middleware that you'll need to create yourself in Laravel in order to handle everything:
The endpoint definition:
Route::post('authenticate', [TokenController::class, 'login']);
The controller:
class TokenController extends Controller
{
public function login(Request $request)
{
// First read the token and decrypt it.
// Here you'll need to replace "some_decryption()" with the required decrypter based on how your VueJS app encrypts the token.
$token = some_decryption( $request->input('token') );
// Then make the request to the verification API, for example using Guzzle.
$isTokenOk = Http::post('http://your-endpoint.net', [
'token' => $token,
])->successful();
// Now issue a Laravel API token only if the verification succeeded.
if (! $isTokenOk) {
abort(400, 'Verification failed');
}
// In order to not store any token in a database, I've chosen something arbitrary and reversibly encrypted.
return response()->json([
'api-token' => Crypt::encrypt('authenticated'),
]);
}
}
Subsequent requests should pass the api token in the Authorization header as a Bearer token. And then in the Middleware you'll check for Bearer token and check if it matches our expected value:
class AuthTokenAuthenticationMiddleware
{
public function handle($request, Closure $next)
{
$authToken = $request->bearerToken();
if (! $authToken || ! Crypt::decrypt($authToken) === 'authenticated') {
abort(401, 'Unauthenticated');
}
return $next($request);
}
}
The Middleware needs to be registered in app/Http/Kernel.php:
protected $routeMiddleware = [
...
'auth-token' => AuthTokenAuthenticationMiddleware::class,
];
And finally apply this new middleware to any of your routes that should be authenticated:
Route::middleware('auth-token')->get('some/api/route', SomeController::class);
Warning: this authentication mechanism relies on reversible encryption. Anyone able to decrypt or in possession of your APP_KEY will ultimately be able to access your protected endpoints!
Of course this is one way to deal with custom userless authentication and there are many more. You could for example insert an expiration date in the encrypted token instead of the string "authenticated" and verify if it's expired in the middleware. But you get the gist of the steps to be followed...
If you do have a User model in place, then you could use Laravel Sanctum and issue an API token after User retrieval instead of forging a custom encrypted token. See https://laravel.com/docs/8.x/sanctum#issuing-mobile-api-tokens
// Fetch the corresponding user...
$user = User::where('token', $request->input('token'))->first();
return $user->createToken('vuejs_app')->plainTextToken;
Subsequent requests should pass the token in the Authorization header as a Bearer token.
Protect routes using the middleware provided by Sanctum:
Route::middleware('auth:sanctum')->get('some/api/route', SomeController::class);
I created an SPA with Laravel 5.6, Vue 2.5 and Laravel Passport which is working quite well. I really love Laravel and Vue as they make building SPAs with APIs very easy and a lot of fun.
After setting up Laravel Passport as described in the docs the login as well as calls to the API are working as expected based on the 'laravel_token' which is correctly returned and stored in the cookie.
However, my problem is that my users are using the app for a pretty long time, without reloading the page but only performing calls against the API with axios. Somehow Laravel does not refresh the 'laravel_token' (and the corresponding cookie) in API calls (it does so, when I call a 'web' route). Consequently, the 'laravel_token' expires t some point and the user needs to log in again.
How can I force Laravel to refresh the 'laravel_token' (and thereby prolong its validity) with every call of an API route from axios?
Any help is very much appreciated!
I solved a similar issues in the past by creating a simple route (in the web middleware group) to keep the session alive for as long as the browser tab is open.
In routes/web.php:
Route::get('/keep-alive', function () {
return response()->json(['ok' => true]);
})->middleware('auth');
And then ping this route periodically with javascript:
setInterval(() => {
axios.get('/keep-alive')
.then(() => {})
.catch(() => {})
}, 600000)
I go into a little more detail here: https://stackoverflow.com/a/57290268/6038111
Axios has a way to "intercept" / see if a call failed. Inside the error callback I am seeing if it was an unauthenticated error and simply reloading the page.
Admittedly, I would love to be able to write another Axios call inside the error caught block to grab another session token "laravel_token", but have yet to find a way to do it. Reloading the page will refresh the laravel_token though, so it solves my problem for now. ¯\_(ツ)_/¯
After-thought: I'm actually thinking you probably couldn't refresh the laravel_token through an Axios call because you'e already dropped the session. I'm guessing you have to do it this way.
// Refresh Laravel Session for Axios
window.axios.interceptors.response.use(
function(response) {
// Call was successful, don't do anything special.
return response;
},
function(error) {
if (error.response.status === 401) {
// Reload the page to refresh the laravel_token cookie.
location.reload();
}
// If the error is not related to being Unauthorized, reject the promise.
return Promise.reject(error);
}
);
I'm trying to implement private channel authorization with Pusher and Laravel.
The forms require a CSRF input field (randomized input name and value). Normally I use twig to insert them into the forms I put on the page.
How can I insert the csrf fields into the form data that Pusher sends when it tries to connect to the auth endpoint? It isn't present in the form data (but is present in the request header), so it's getting rejected by the laravel CSRF middleware.
If you're using Laravel, this isn't necessary, you shouldn't implement your auth endpoint like this. Your auth endpoint should be defined inside channels.php in the routes folder. For example
// routes/channels.php
Broadcast::channel('chat', function ($user) {
return Auth::check();
});
CSRF not necessary.
I'm sending an ajax post request, and with Laravel it seems that is done by creating a post route for it. I've set it up so a csrf token is put in the header automaticaly for every ajax request using ajaxSetup. I'm attempting to then catch that header on the backend and verify the tokens match.
In my web routes (which automatically use the web middleware), this returns as expected:
Route::get('/test', function() {
return csrf_token();
});
However, when I post to a route via AJAX, like either of the below ways:
Attempt 1:
Route::post('/test', 'AjaxController#test');
In the AjaxController construct, followed by an alert in the view:
var_dump(csrf_token().',hi'); die;
Response: ',hi' (csrf_token was null).
Attempt 2:
Route::post('/test', ['test' => csrf_token().',hi', 'uses' => 'AjaxController#test']);
$test = $request->route()->getAction()['test'];
var_dump($test); die;
Response: ',hi' (csrf_token was null).
What I seem to be running into is, with get requests csrf_token() is populated, on my post request, it is not.
Any ideas?
check your route group it must apply the web middleware as
Route::group(['middleware' => 'web'], function () {
Route::get('/test', function() {
return csrf_token();
//or return $request->session()->token();
});
});
Finally figured this out.
CSRF can indeed be checked on an ajax post request. I wanted to make sure someone on their own site isn't hitting my ajax endpoint with any success of doing anything, especially for another user.
However, I ran into a Laravel order of operations issue, with the way Laravel sets up the session. I was trying to call a validation method (within in the same class) in the constructor, where I validated for CSRF and verified the requesting user all in one place. I wanted to do this so that any time someone hits this class, I didn't have to call the verification in each public method in the class, I'd only have to call it once.
However, csrf_token(), and the request session in general, is not available to me yet in my construct. It is, however, available to me in the method within the controller class that is called in the route.
For example, given the following route:
Route::post('/test', 'AjaxController#test');
If I injected Request into the construct and then tried to reference anything in the session (in the construct), or get the value of csrf_token(), it will throw an error, because Laravel hasn't set that stuff up yet. But if I reference either of those things in the test method, it'll be there and available just fine.
A bit of a weird Laravel order of operations issue.
csrf protections are managed by Laravel Forms. It won't be available when dealing with APIs.
You should have a look at how middlewares are used in Laravel
https://laravel.com/docs/5.4/middleware
Think using API middleware for your APIs ;)
If you run this command php artisan make:auth documented here https://laravel.com/docs/5.4/authentication#authentication-quickstart when going to resources/views/layouts/app.blade.php you'll see this:
<meta name="csrf-token" content="{{ csrf_token() }}">
And in app.js
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content')
}
});
In 5.3 there was this cool feature which looks as though it has since been removed in 5.4.
<script>
window.Laravel = <?php echo json_encode([
'csrfToken' => csrf_token(),
]); ?>
</script>
So what you need to do is add the csrf field to every request. Do the first 2 code snippets and you'll be fine. The 3rd I believe is probably for Vue.
Answer to your question: no, no, no and no. CSRF tokens I wouldn't believe are generated in POST requests, it's a Cross site Reference token, not an authentication token. If you're looking for something like authentication token refreshing then checkout JWT although the packages for JWT for laravel are a bit unfinished at the moment; with a little work you can get them working.
https://github.com/tymondesigns/jwt-auth 1.0.*#dev is pretty good. You can then use their refresh middleware to generate new tokens on request but this is quite advanced and unless it's for authentication then I wouldn't bother really.
I believe Dingo (another work in progress I believe) https://github.com/dingo/api uses the above package
Anything else let me know!
I'm writing an API in Slim 3 that integrates with a legacy system. The client sends a token to my API in the querystring. I want to write a middleware that will authenticate the token and return an object that contains the necessary internal login credentials (which are different from the token) that are used by the legacy system.
I can authenticate the token now, but my problem is that Slim 3 requires that the middleware return a \Psr\Http\Message\ResponseInterface instance. I also want it to return a custom object back to the application.
I think I can achieve this by re-verifying the token outside of the middleware, and only use the middleware as a way to authenticate the token and return an error if it fails. I tend to think this kludgy way could be avoided so I just have to use the token once in the middleware and return the custom object at the same time so I don't have to use the token twice.
I have searched around for solutions, but all of the example middlewares I can find are similar to https://github.com/julionc/slim-basic-auth-middleware, where they are simply authenticating in the middleware but do not have the requirement to return a custom object. The documentation at http://www.slimframework.com/docs/concepts/middleware.html doesn't seem to help much either with this custom requirement.
Any ideas?
You could include a callback in your middleware which you can use to store the custom object somewhere. For example with slim-jwt-auth you can use callback to store the decoded contents of JWT using a callback.
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => "supersecretkeyyoushouldnotcommittogithub",
"callback" => function ($request, $response, $arguments) use ($app) {
$app->jwt = $arguments["decoded"];
}
]));
Note that this kind of callback is not a Slim 3 feature. Just something this middleware happens to use.