Laravel Api
step 1
create a new laravel project
laravel new LaravelApi
step 2
Install laravel fortify
composer require laravel/fortify
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
step 3
Add sanctum middleware to api middleware group in your
app/Http/Kernel.php
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Session\Middleware\StartSession::class
],
step 4
add FortifyServiceProvider class to your conig/app.php file
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\FortifyServiceProvider::class,
step 5
create .env file and add the url to your SPA apllication
SPA_URL=http://localhost:3000
Here our SPA is running on port 3000.
step 6
In your config/fortify.php replace this line
'home' => RouteServiceProvider::HOME,
by
'home' => env('SPA_URL'),
so that you can be redirect to your SPA root page after logging.
PS: Feel free to redirect the user to another route on your SPA after logging
Example if you want to redirect your user to ‘/dashbord’ route after logging just do like this
'home' => env('SPA_URL') . ‘/dashbord’,
After turn also the views to false as we don’t need these routes in our project
>> 'views' => false,
step 7
Change your routes/api.php to
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Laravel\Fortify\Http\Controllers\RegisteredUserController;
use Laravel\Fortify\Http\Controllers\AuthenticatedSessionController;
use Laravel\Fortify\Http\Controllers\PasswordResetLinkController;
use Laravel\Fortify\Http\Controllers\NewPasswordController;
use Laravel\Fortify\Features;
/*
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
// Register
if (Features::enabled(Features::registration())) {
Route::post('/register', [RegisteredUserController::class, 'store'])
->middleware(['guest:'.config('fortify.guard')]);
}
// Login
$limiter = config('fortify.limiters.login');
Route::post('/login', [AuthenticatedSessionController::class, 'store'])
->middleware(array_filter([
'guest:'.config('fortify.guard'),
$limiter ? 'throttle:'.$limiter : null,
]));
// Reset Password
if (Features::enabled(Features::resetPasswords())) {
Route::post('/forgot-password', [PasswordResetLinkController::class, 'store'])
->middleware(['guest:'.config('fortify.guard')]);
Route::post('/reset-password', [NewPasswordController::class, 'store'])
->middleware(['guest:'.config('fortify.guard')]);
}
step 8
Manage reset password link which will be send to your email address by changing your
**App\Providers\AuthServiceProvider ** to
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Auth\Notifications\ResetPassword;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
ResetPassword::createUrlUsing(function (object $notifiable, string $token) {
return env('SPA_URL') . '/reset-password?token=' . $token . "&&email=" .$notifiable->getEmailForPasswordReset();
});
}
}
step 9
Add mailgun mail service provider to send rest password email to your users
in your ** .env** file add
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=your_mailGun_smtp_username
MAIL_PASSWORD=your_mailGun_smtp_password
MAIL_ENCRYPTION=tls
MAIL_FROM_NAME="${APP_NAME}"
MAIL_FROM_ADDRESS=test@test.com
PS: Sanbox subdomain is used for test only so to do so you have to register your receiver’s email address so that the mail can be sent.
Nuxt js SPA
step 1
Create your nuxtjs project
yarn create nuxt-app NuxtSpa
step 2
Add dotenv package
yarn add dotenv
In your nuxt.config.js file
add before the ‘export default{}’ the following line
require("dotenv").config();
step 3
create a .env file and add this
API_URL = http://localhost:8000/api
APP_ENV=local
APP_NAME=NuxtSpa
PS: API_URL is the url of your laravel Api backend
Now in your nuxt.config.js file add the following line of code:
env: {
baseUrl: process.env.API_URL || 'laravel Api'
}
step 4
Create a new folder shared/config and create the file api.config.js with the following code
export const API_URL = process.env.baseUrl;
step 5
In the store folder
create a new folder called auth comtaining
actions.js file with the code
import { AUTHENTICATION_MODULE_MUTATIONS } from "./mutations";
import authService from "~/shared/services/auth.service";
export const AUTHENTICATION_MODULE_ACTIONS = {
REGISTER: "register",
LOGIN: "login",
SEND_RESET_PASSWORD_EMAIL: "send_reset_password_email"
};
export default {
[AUTHENTICATION_MODULE_ACTIONS.REGISTER]({ commit }, { email,name, password }) {
return new Promise((resolve, reject) => {
authService
.register({ email, name, password })
.then((response) => {
commit(
AUTHENTICATION_MODULE_MUTATIONS.SET_IS_AUTHENTICATED);
})
.catch((error) => reject(error));
});
},
[AUTHENTICATION_MODULE_ACTIONS.LOGIN]({ commit }, { email, password }) {
return new Promise((resolve, reject) => {
authService
.login({ email, password })
.then((response) => {
commit(
AUTHENTICATION_MODULE_MUTATIONS.SET_IS_AUTHENTICATED);
})
.catch((error) => reject(error));
});
},
[AUTHENTICATION_MODULE_ACTIONS.SEND_RESET_PASSWORD_EMAIL]({ commit }, { email }) {
return new Promise((resolve, reject) => {
authService
.send_reset_password_email({ email })
.then((response) => {
console.log(response.data)
})
.catch((error) => reject(error));
});
},
[AUTHENTICATION_MODULE_ACTIONS.RESET_PASSWORD]({ commit }, { email,password,password_confirmation,token }) {
return new Promise((resolve, reject) => {
authService
.reset_password({ email,password,password_confirmation,token })
.then((response) => {
console.log(response.data)
})
.catch((error) => reject(error));
});
},
};
export const getAuthModule = "auth";
export const getAuthModuleComponent = (key) => `auth/${key}`;
getters.js with the code
export const AUTHENTICATION_MODULE_GETTERS = {
ERRORS: "errors",
IS_AUTHENTICATED: "isAuthenticated",
};
export default {
errors: (state) => state.errors,
isAuthenticated: (state) => state.isAuthenticated,
};
mutaions.js with the code
export const AUTHENTICATION_MODULE_MUTATIONS = {
SET_ERRORS: "setErrors",
SET_IS_AUTHENTICATED: "setIsAuthenticated",
};
export default {
[AUTHENTICATION_MODULE_MUTATIONS.SET_ERRORS](state) {
state.errors = true;
},
[AUTHENTICATION_MODULE_MUTATIONS.SET_IS_AUTHENTICATED](state) {
state.isAuthenticated = true;
},
};
state.js withe code
const inBrowser = typeof window !== "undefined";
const defaultState = { errors: null, isAuthenticated: false };
export default () =>
inBrowser && window.__INITIAL_STATE__ ? window.__INITIAL_STATE__.page : defaultState;
In your store/index.js add the following code
import createLogger from "vuex/dist/logger";
import { getAuthModuleComponent } from "./auth/actions";
import { AUTHENTICATION_MODULE_MUTATIONS } from "./auth/mutations";
const debug = process.env.NODE_ENV !== "production";
export const plugins = debug ? [createLogger()] : [];
export const actions = {
nuxtServerInit({ commit }, { req }) {
if (req.session) {
commit(getAuthModuleComponent(AUTHENTICATION_MODULE_MUTATIONS.SET_IS_AUTHENTICATED));
}
},
};
step 6
In your shared folder create a new folder named as services
In this create a new file api.service.js with the following code
import axios from 'axios'
const apiService = {
init() {
axios.defaults.withCredentials = true
},
setHeader() {
axios.defaults.headers.common['X-Api-Client'] = `web`
},
query(resource, config) {
this.setHeader()
return axios.get(resource, config).catch((error) => {
return Promise.reject(error)
})
},
get(resource) {
this.setHeader()
return axios.get(`${resource}/`).catch((error) => {
return Promise.reject(error)
})
},
post(resource, data, config) {
this.setHeader()
return axios.post(`${resource}`, data, config).catch((error) => {
return Promise.reject(error)
})
},
patch(resource, data, config) {
this.setHeader()
return axios.patch(`${resource}`, data, config).catch((error) => {
return Promise.reject(error)
})
},
delete(resource) {
this.setHeader()
return axios.delete(resource).catch((error) => {
return Promise.reject(error)
})
},
}
export default apiService
In the same services folder create a auth.service.js file with the following code:
import apiService from "./api.service";
import { API_URL } from "~/shared/config/api.config";
const authService = {
async register({ email, name, password }) {
return await apiService.post(`${API_URL}/register`, {
name,
email,
password,
});
},
async login({ email, password }) {
return await apiService.post(`${API_URL}/login`, {
email,
password,
});
},
async send_reset_password_email({ email }) {
return await apiService.post(`${API_URL}/forgot-password`, {
email,
});
},
async reset_password({ email, password, password_confirmation, token }) {
return await apiService.post(`${API_URL}/reset-password`, {
email,
password,
password_confirmation,
token
});
}
}
export default authService;
step 7
In your pages folder:
create a register.vue file
<template>
<div class="bg-white py-6 sm:py-8 lg:py-12">
<div class="max-w-screen-2xl px-4 md:px-8 mx-auto">
<h2
class="
text-gray-800 text-2xl
lg:text-3xl
font-bold
text-center
mb-4
md:mb-8
"
>
Register
</h2>
<form class="max-w-lg border rounded-lg mx-auto">
<div class="flex flex-col gap-4 p-4 md:p-8">
<div>
<label
for="email"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>Name</label
>
<input
v-model="form.name"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<div>
<label
for="email"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>Email</label
>
<input
v-model="form.email"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<div>
<label
for="password"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>Password</label
>
<input
v-model="form.password"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<nuxt-link to="/login">
<span
class="bg-indigo-500 cursor-pointer flex justify-center text-white py-4 rounded-md"
@click.prevent="submit"
>Register</span
>
</nuxt-link>
</div>
<div class="flex justify-center items-center bg-gray-100 p-4">
<p class="text-gray-500 text-sm text-center">
Already have an account?
<a
href="#"
class="
text-indigo-500
hover:text-indigo-600
active:text-indigo-700
transition
duration-100
"
>Login</a
>
</p>
</div>
</form>
</div>
</div>
</template>
<script>
import {getAuthModuleComponent, AUTHENTICATION_MODULE_ACTIONS} from "../store/auth/actions"
export default {
data() {
return {
form: {
name: '',
email: '',
password: '',
},
}
},
methods: {
async submit() {
try {
await this.$store.dispatch(
getAuthModuleComponent(AUTHENTICATION_MODULE_ACTIONS.REGISTER),
{ email: this.form.email, password: this.form.password, name: this.form.name },
);
} catch (error) {
return error;
}
},
},
}
</script>
create a login.vue file
<template>
<div class="bg-white py-6 sm:py-8 lg:py-12">
<div class="max-w-screen-2xl px-4 md:px-8 mx-auto">
<h2
class="
text-gray-800 text-2xl
lg:text-3xl
font-bold
text-center
mb-4
md:mb-8
"
>
Register
</h2>
<form class="max-w-lg border rounded-lg mx-auto">
<div class="flex flex-col gap-4 p-4 md:p-8">
<div>
<label
for="email"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>Email</label
>
<input
v-model="form.email"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<div>
<label
for="password"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>Password</label
>
<input
v-model="form.password"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<span
class="bg-indigo-500 cursor-pointer flex justify-center text-white py-4 rounded-md"
@click.prevent="submit"
>Login</span
>
</div>
<div class="flex justify-center items-center bg-gray-100 p-4">
<p class="text-gray-500 text-sm text-center">
Don't have an account?
<a
href="#"
class="
text-indigo-500
hover:text-indigo-600
active:text-indigo-700
transition
duration-100
"
>Register</a
>
</p>
</div>
</form>
</div>
</div>
</template>
<script>
import {getAuthModuleComponent, AUTHENTICATION_MODULE_ACTIONS} from "../store/auth/actions"
export default {
data() {
return {
form: {
email: '',
password: '',
},
}
},
methods: {
async submit() {
try {
const isNewUser = await this.$store.dispatch(
getAuthModuleComponent(AUTHENTICATION_MODULE_ACTIONS.LOGIN),
{ email: this.form.email, password: this.form.password },
);
console.log(isNewUser);
} catch (error) {
return error;
}
},
},
}
</script>
create a send-reset-password-email.vue file
<template>
<div class="bg-white py-6 sm:py-8 lg:py-12">
<div class="max-w-screen-2xl px-4 md:px-8 mx-auto">
<h2
class="
text-gray-800 text-2xl
lg:text-3xl
font-bold
text-center
mb-4
md:mb-8
"
>
Send Email
</h2>
<form class="max-w-lg border rounded-lg mx-auto">
<div class="flex flex-col gap-4 p-4 md:p-8">
<div>
<label
for="email"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>Email</label
>
<input
v-model="form.email"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<span
class="bg-indigo-500 cursor-pointer flex justify-center text-white py-4 rounded-md"
@click.prevent="submit"
>Send</span
>
</div>
</form>
</div>
</div>
</template>
<script>
import {getAuthModuleComponent, AUTHENTICATION_MODULE_ACTIONS} from "../store/auth/actions"
export default {
data() {
return {
form: {
email: '',
password: '',
},
}
},
methods: {
async submit() {
try {
const isNewUser = await this.$store.dispatch(
getAuthModuleComponent(AUTHENTICATION_MODULE_ACTIONS.SEND_RESET_PASSWORD_EMAIL),
{ email: this.form.email },
);
console.log(isNewUser);
} catch (error) {
return error;
}
},
},
}
</script>
create a reset-password.vue file
<template>
<div class="bg-white py-6 sm:py-8 lg:py-12">
<div class="max-w-screen-2xl px-4 md:px-8 mx-auto">
<h2
class="
text-gray-800 text-2xl
lg:text-3xl
font-bold
text-center
mb-4
md:mb-8
"
>
Reset password
</h2>
<form class="max-w-lg border rounded-lg mx-auto">
<div class="flex flex-col gap-4 p-4 md:p-8">
<div>
<label
for="email"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>Email</label
>
<input
v-model="form.email"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<div>
<label
for="email"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>New password</label
>
<input
v-model="form.password"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<div>
<label
for="email"
class="inline-block text-gray-800 text-sm sm:text-base mb-2"
>Confirm password</label
>
<input
v-model="form.password_confirmation"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
</div>
<input
v-model="form.token"
type="hidden"
class="
w-full
bg-gray-50
text-gray-800
border
focus:ring
ring-indigo-300
rounded
outline-none
transition
duration-100
px-3
py-2
"
/>
<span
class="bg-indigo-500 cursor-pointer flex justify-center text-white py-4 rounded-md"
@click.prevent="submit"
>Send</span
>
</div>
</form>
</div>
</div>
</template>
<script>
import {getAuthModuleComponent, AUTHENTICATION_MODULE_ACTIONS} from "../store/auth/actions"
export default {
data() {
return {
form: {
email: this.$route.query.email,
password: '',
token: this.$route.query.token,
password_confirmation: ''
},
}
},
methods: {
async submit() {
try {
const isNewUser = await this.$store.dispatch(
getAuthModuleComponent(AUTHENTICATION_MODULE_ACTIONS.RESET_PASSWORD),
{ email: this.form.email, password: this.form.password, password_confirmation: this.form.password_confirmation, token: this.form.token },
);
console.log(isNewUser);
} catch (error) {
return error;
}
},
},
}
</script>
Hope it helps you 😊