How to use Laravel and Nuxtjs with Authentication, including login, logout, and password resets? - laravel

I can't find any resource to help with this issue, there are some repos that provide some type of base, but not many of them actually work.
Goals: Run Laravel has the backend API, run NuxtJS as the frontend SPA, either 2 separate locations or combined into one.
Needs to have proper authentication between both systems for logging in users. Laravel Sanctum looks to solve some of the SPA issues, but its hard to find proper documentation that actually shows a fully setup example.
Tested this idea https://github.com/zondycz/laravel-nuxt-sanctum But has failed, doesnt work with npm, must use yarn, however, login errors, doesnt work out of box. Repo needs work.
This tutorial Secure authentication in Nuxt SPA with Laravel as back-end was very indepth, and looked promising, however, refresh tokens don't seem to work with the SPA side since the author developed it on static nuxt. Though I feel like it could be modified to work, I havent found the solution yet.
This template, Laranuxt looked very promising, though I've yet to try it, im not sure if they are regularly updating it at this point, which was previously built by Laravel Nuxt JS (abandoned project)
I was able to run the #2, while refresh tokens dont exactly work, I can still authenticate the user, but now the other issue is password resets, which I'm unable to properly setup through the nuxt form.
Has anyone found resources or solved this issue with communication between these frameworks? or am I going done a rabbit hole that seems to have no end in sight?
I guess another way could be saying, can you do a fully restful authentication system?
Hopefully this isn't too broad of a topic, looking for some guidance on this issue as its hard to find proper tutorials or documentation without writing too much core code myself.

It seems many people struggle to implement Sanctum for SPA authentication when splitting the front and back across separate domains, and the problem is usually CORS related. The Sanctum documentation is great, but assumes a knowledge of CORS (or assumes requests will be same-origin). I'll break down the setup as I see it, providing a little extra support where I feel the docs fall short. A long answer, but towards the end I will address your question which seems to focus specifically on authentication.
Taken from Sanctum documentation:
First, you should configure which domains your SPA will be making requests from.
Assuming your front-end app lives at https://www.my-awesome-app.io, what is the domain? What about http://localhost:3000? Domains map to IP addresses, not protocols or port numbers. So the domains in the given examples would be www.my-awesome-app.io and localhost. With that in mind, all you need to do at this stage is go to the sanctum.php file in your config directory and set the value of the 'stateful' key to match the domain your Laravel API will receive requests from. Although domain names by definition do not include port numbers, the Sanctum docs make it very clear this is also required if you're accessing via a URL that requires a specific port.
/config/sanctum.php
...
'stateful' => [
'localhost:3000',
],
or
'stateful' => [
'my-awesome-app.io',
],
.env files are useful here.
If you are having trouble authenticating with your application from an SPA that executes on a separate subdomain, you have likely misconfigured your CORS (Cross-Origin Resource Sharing) or session cookie settings.
Indeed. So what does a correct setup look like? Assuming a recent Laravel version using the fruitcake/laravel-cors package, you will have a cors.php file in your /config folder. The default looks like:
Default
/config/cors.php
...
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
We have some work to do here. First, the paths. At the moment, our Laravel API is set up to allow requests from any external origin only if they are trying to access the /api/ routes*. This can lead to trouble early on when, as the Sanctum docs require, we try to access a csrf cookie from the path /sanctum/csrf-cookie. Requests to this path are not explicitly permitted in our cors.php file, so they will fail. To fix, we could do this:
'paths' => [
'api/*',
'sanctum/csrf-cookie'
]
and now requests to /sanctum/csrf-cookie will be permitted. As a sidenote, I find it personally very useful to change the prefix from sanctum to api, that way I can set a single base url for my http client (usually axios).
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'http://localhost:3000/api';
To change the path, you can change the following in the /config/sanctum.php file:
'prefix' => 'api',
Now GET requests to /api/csrf-cookie will return the cookie, instead of /sanctum/csrf-cookie.
Next, the allowed-origins. By default, it is set to *, which means "any origin". An origin is the protocol, domain and port number of the app sending a request to your Laravel API. So going back to our earlier examples, their origins would be http://localhost:3000 and https://www.my-awesome-app.io. These are the exact values you should use to allow requests from your front-end app:
'allowed_origins' => ['http://localhost:3000'],
I would recommend moving this to the .env file, and having a separate origin for local and production.
/config/cors.php
...
'allowed_origins' => [env('ALLOWED_ORIGINS')],
/.env
...
ALLOWED_ORIGINS=http://localhost:3000
The documentation does mention the last part of our cors config, which is that
'supports_credentials' => false,
Must be changed to:
'supports_credentials' => true,
Our /config/cors.php file now looks like:
Modified
/config/cors.php
...
'paths' => [
'api/*',
'sanctum/csrf-cookie'
],
'allowed_methods' => ['*'],
'allowed_origins' => [env('ALLOWED_ORIGINS')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
Bonus info, Chrome will not allow a credentialed request to a server that returns the header
Access-Control-Allow-Origin: *
Google Chrome: A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true
So you should make sure you set a specific origin in your cors config!
Finally, you should ensure your application's session cookie domain configuration supports any subdomain of your root domain. You may do this by prefixing the domain with a leading . within your session configuration file:
This isn't complicated, but seems it can catch people out so thought I'd mention it. Given our examples so far, we'd make the following change to our config/session.php file:
'domain' => '.my-awesome-app.io',
Locally, localhost alone is fine:
'domain' => 'localhost',
Assuming you've followed the rest of the instructions in the Sanctum documentation (setting axios.defaults.withCredentials = true;, adding the middleware etc) your backend configuration is now complete.
Front end and authentication.
I love Sanctum and I'm very grateful for the creators; so I say this with respect; the documentation lacks a little depth at this point. Grabbing the csrf-token is very straight forward, and then...
Once CSRF protection has been initialized, you should make a POST request to the typical Laravel /login route. This /login route may be provided by the laravel/jetstream authentication scaffolding package.
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.
It seems they've updated the docs!
As I write this, I've checked the latest docs and it's now highlighted the fact that you are free to write your own login endpoint. This was always the case, but might have escaped a few people, perhaps given the instructions above ("you should make a POST request to the typical Laravel /login route.") It's also perhaps not clear that you can override default Laravel methods to prevent unwanted side-effects of the default auth setup, like redirecting to the /home page etc.
Writing your own login controller is simple, and I prefer to do so for Sanctum. Here's one you can use:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
public function login(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
'password' => 'required'
]);
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
return response()->json(Auth::user(), 200);
}
throw ValidationException::withMessages([
'email' => 'The provided credentails are incorect.'
]);
}
}
Feel free to modify this to suit your needs.
How you manage your state (making sure your app remembers you are logged in, for example) on the front-end is also up to you. There are lots of options, however if we're using Sanctum I think we should focus on a simple cookie-based approach. Here's one I use:
Login to your app. An auth session is established and your browser saves the cookies provided by your Laravel API.
Have your login script return the authenticated user (the one provided above does just that). Save the details of that user to your app state (eg. Vuex).
Check your state contains the user any time you need to secure an action against unauthorised users. Redirect to the login page is the auth check fails.
Here's the above in Nuxt.js form using middleware.
/middleware/auth-check.js
export default async function ({ store, redirect }) {
// Check if the user is not already in the store.
if (store.state.user === null) {
// Call your Laravel API to get the currently authenticated user.
// It doesn't matter if the store has been wiped out due to a page
// refresh- the browser still has the cookies, which will be sent
// along with this request.
try {
let rsp = await user.getAuthenticatedUser()
// If we get the user from the Laravel API, push it back in to
// the store and carry on to the page.
store.commit('SET_AUTH_USER', rsp.data)
} catch (e) {
// If our API doesn't return the user for any reason, redirect to
// the login page.
return redirect('/login')
}
}
// If not, carry on to the page.
}
/pages/admin.vue
export default {
middleware: auth-check
}
The code above is for example purposes, but it's generally what I use for Vue/Nuxt and Sanctum.
Hope this helps, happy to elaborate further if anyone can benefit.

Related

Laravel 9 SPA athentication with nuxt, is saving tokens and sessions in cookies safe?

So I am making a web app with nuxt 3 and laravel, and I cant seem to get the SPA authentication to work. So I am for now generating a usertoken and saving it in the cookies, so my user keeps logged in. But I am doubting that this is a safe way to keep my user authenticated and do other actions. I tried looking up whether it is good to store it in cookies. But there are to many mixed reviews on this subject.
(the reason why I personally storing it in the cookies, is because nuxt3 is ssr and I wont be able to get the token if it was stored in the localstorage)
I know that cookies are more venerable to xsrf attacks and localstorage to xss attacks.
But what I understand when I am going to use the SPA authentication of laravel, those sessions and tokens are going to be saved in the cookies as well right? So what difference does that make with saving a token in the cookies?
TLDR: store it in the cookies + add some flags to it.
Here is my other answer, HTTPOnly and with Secure adds a layer of security to it.
And that one should probably be totally fine.
XSRF is something that should not happen on your website but if somebody got caught by phishing, you can't really protect the website more than that.
The security realm is a big thing, a system will never be bullet-proof: it's more of a matter of following the best practices as much as possible.
You can mitigate the damages but assigning a short TTL to your tokens and checking their viability on a middleware that runs on every client-side navigation.
I'm not an expert in security but you will probably not need one either. Follow that advice and consider your job done.
Social engineering + other breaches can lead to your website being breached in far easier ways.
If you are a big corporate company that needs those drastic security measures, you also have probably spent millions on ways to lock your network security up.
If your Laravel api(assuming you are using Laravel as API) and SPA(Nuxt) are on the same domain, You can use Sanctum cookie-based session authentication.
#1. Uncomment EnsureFrontendRequestsAreStateful middleware in app/Http/Kernel.php
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
#2. Add SANCTUM_STATEFUL_DOMAINS in your .env file
SANCTUM_STATEFUL_DOMAINS=localhost:3000
#3. In config/cors.php, change support credentials to true
'supports_credentials' => true,
#4. Before you hit login endpoint make request to 'sanctum/csrf-cookie' and then POST request to login endpoint
// Sanctum endpoint to generate cookie
Route::get('sanctum/csrf-cookie', function () {
return response('OK', 204);
});
Route::controller(AuthController::class)->group(function () {
Route::post('auth/login', 'login');
});
#5. Enable the 'withCredentials' option on your application's global axios instance
axios.defaults.withCredentials = true;

Laravel - Protect API routes

I have Laravel application with VUEJS as front-end,
I am getting data by creating API Routes.
So for example the route for getting posts data will be http://localhost/api/posts
What is the best way to protect my routes?
I saw on laravel documentation that there is:
API athentication https://laravel.com/docs/5.8/api-authentication
also Passport https://laravel.com/docs/5.8/passport
For example now any user can reach to the route http://localhost/api/posts
and he will get json with all posts data.
I want protect that and allow only inner api request from my VUEJS commponent to get the data
I’m assuming you’re going to use the Laravel auth routes to do the authentication, and after the authentication, the next view you’re reaching is the one with all the Vue components.
The solution is simple, even that is on the documentation, the necessary steps should be clarified.
We need to:
Add passport composer require laravel/passport
Make the migrations php artisan migrate
Install passport php artisan passport:install
The fourth step is more complex. We need to open our User.php model file. And first we need to import the HasApiTokens and tell the model to use it.
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
.......
}
Then on our config/auth.php we need to modify the api array and change the driver to passport
'api' => [
//for API authentication with Passport
'driver' => 'passport',
'provider' => 'users',
],
Then on our app/Http/Kernel.php we need to add a middleware to the $middlewareGroups array in the key web.
protected $middlewareGroups = [
'web' => [
................
//for API authentication with Passport
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
Now we can use the auth:api middleware on our api routes.
Route::middleware('auth:api')->group( function(){
...your routes here
});
This is what the CSRF TOKEN doing, it's not quite the same with the API Authorization doing
CSRF Token:
To protect (inner) API or access points from cross-site accessing, See Cross-site_request_forgery
CSRF Token is expired and generated within a randomly time, which will make the program access difficulty
API Authorization:
The API is design to be used from other programs, and you'd like to protect them from non-authorized access
Since API tokens expiration and generation is handle by admin manually, since you'll need to place this API token in your HTML to get your function working, it's not what you searching for here
More details of CSRF protection in Laravel see: Laravel CSRF production document
Generally, we'll protect all the routes POST and PUT routes by default

Laravel combine Passport authentication and normal authentication

How do I combine Passport authentication and normal laravel authentication?
I want the user to be logged in on pages of web-middleware and api-middleware. The login route is in api-middleware. Currently I have set up Passport authentication and it works fine for all api-middleware routes. How to make the user logged in in web-middleware as well?
Edit #1
What Im doing:
Login code
$http = new \GuzzleHttp\Client();
try {
$response = $http->post(config('services.passport.login_endpoint'), [
'form_params' => [
'grant_type' => 'password',
'client_id' => config('services.passport.client_id'),
'client_secret' => config('services.passport.client_secret'),
'username' => $args['email'],
'password' => $args['password']
]
]);
$user = User::where('email', $args['email'])->first();
Auth::guard('web')->login($user);
return [
"token" => $response->getBody()->getContents(),
"user" => $user
];
} // ...
Somewhere in some web-middleware route
return auth()->check() ? "logged in" : "not logged in";
returns "not logged in"
Ideally you shouldn't, as passport auth is for a separate app communicating to the API and laravel preshipped auth is for MVC, they are separate user sessions.
But assuming you know what you are doing, either call Auth::login($user); on user login via API, or generate the passport token when the user login through web middleware auth, whichever login happens first...
Remember Auth::login($user); creates a user session and sets cookies to refer to that session... So you create for yourself a new problem were on logout, there are two places to logout from... as technically the user is logged in twice, with passport token and with a cookie referring to his session...
Actually I'm in a situation like you were. I have searched a lot about it. I always needed web authentication because of nature of my projects but in addition I started to develop projects with api backend soo late in comparing with web development world.
I'm a bit lazy so I generally use Laravel Passport and without working always out of the box, it does the job so in my opinion if you want just the functionality of access tokens for api security, put your user login authentication on web side and just authenticate the api endpoints with auth:api middleware in your api.php file.
I know that that's not the best practice but since it sounds that you are not developing a pure Laravel SPA then you can follow the route for Laravel Multipage application with Vue powered.
But believe me best way is to use either web authentication or api authentication not together then as the above answer says, you will have two authentication working at the same time which does not sound ok.
At the end, when you want to get the authenticated user on blade templates you will use
auth()->user()
but on the other hand in api controllers you should use
auth('api')->user()
which is nice to have but dangerouse to use.
If you need to log an existing user instance into your application, you may call the login method with the user instance.
Auth::login($user);
You can also use the guard() method:
Auth::guard('web')->login($user);
See the documentation here for more information: https://laravel.com/docs/5.8/authentication#authenticating-users

Socialite Google redirect_uri_mismatch

I am trying to login with google using socialite , my facebook login works fine so the problem in my google app, any help!!!
'google' => [
'client_id' => 'app-key',
'client_secret' => 'app-secret',
'redirect' => 'http://www.shoptizer.com/callback/google',
],
Also one more point to remember that Socialite also gives uri_mismatch_error even when your redirects are correctly defined in google console but you dynamically changed the redirectUrl through
return Socialite::with('google')->redirectUrl($redirect_url)->redirect();
So plz take care that you should also need to define while receiving the response
Socialite::driver('google')->redirectUrl($redirect_url)->stateless()->user();
where $redirect_url is your custom redirect url.
After google redirects you to correct place, but even then Socialite checks it at its end.
I found this link https://blog.damirmiladinov.com/laravel/laravel-5.2-socialite-google-login.html
From this tutorial:
Occasionally it happens that google require some time to apply client configuration If you get an error message redirect_uri_missmatch wait couple of minutes and it should work normally.
Also change the redirect uri by:
'google' => [
'client_id' => 'app-key',
'client_secret' => 'app-secret',
'redirect' => 'https://www.shoptizer.com/callback/google',
],
If your app is provided by https you must match your http scheme on google api and on your redirect callback.
The problem is in the default url, you must change it on two occasions: before the redirect and before getting the user data.
Do not do this:
return Socialite::driver('google')->redirectUrl($yourredirecturl)->redirect();
Do it:
config()->set('services.google.redirect', $yourredirecturl);
return Socialite::driver('google')->redirect();
And when accessing user data, do this:
config()->set('services.google.redirect', $yourredirecturl);
$user = Socialite::driver('google')->user();

Laravel api routes with auth

I'm trying to make an api route that's only accessible if the user making the request is logged in. This is what I have in my routes/api.php but it returns
{"error":"Unauthenticated."}
Route::group(['middleware' => ['auth:api'], function () {
Route::post('schedules', ['uses' => 'Api\ScheduleController#store']);
});
Can this be done without laravel passport and how? I only need the route for in-app use for logged in users.
I assumed the login mentioned is on "web" which using "session" as driver.
Your are getting this issue because "web" and "api" guard is using different driver for authentication. Take a look in config/auth.php. The "api" guard is using "token" as it's default driver.
Thus, you have few options to encounter this.
Move the route for "schedules" in web.php. No worry, your ajax will failed if not authenticated. But, take note that anything that involved POST method will require csrf (_token parameter), unless you are using laravel axios
Using authentication using api also which you can refer this tutorial for "token" driver and all your secure routes will be using token in its Authentication header

Resources