How to login/logout using full stack Vue frontend with Laravel backend - vuejs2

I have been struggling night after night trying to implement a complete Vue front-end using a Laravel back-end on the same server. I can get normal resource requests done with axios just fine; however, loggin in and out is a bit of a nightmare! I have exhausted my ideas and really need some help :)
Here is my Login Vue component:
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Login</div>
<div class="card-body">
<form>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">E-Mail Address</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" required autocomplete="email" autofocus v-model="email">
<!-- <span class="invalid-feedback" role="alert">
<strong>message</strong>
</span> -->
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required autocomplete="current-password" v-model="password">
<!-- <span class="invalid-feedback" role="alert">
<strong>message</strong>
</span> -->
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" v-model="remember">
<label class="form-check-label" for="remember">
Remember Me
</label>
</div>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button #click.prevent="login" class="btn btn-primary">
Login
</button>
<a class="btn btn-link" href="">
Forgot Your Password?
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
email: null,
password: null,
remember: null,
loading: false
}
},
methods: {
login() {
window.axios.post('login', {
email: this.email, password: this.password
})
.then(response => {
console.log('Login fired!')
console.log(response.data)
})
.catch(error => {
console.log(error)
})
},
},
}
</script>
Here is my axios config in my 'bootstrap' file which I pull into my app.js:
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios.defaults.baseURL = 'http://localhost:8000/api/';
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
Here is my app.js:
import './bootstrap'
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from './store/store'
import routes from './routes'
import App from './App.vue'
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
routes
})
const app = new Vue({
el: '#app',
router,
store,
render: h => h(App)
});
Now on the back-end side, I have pulled in my 'authenticated' and 'loggedOut' methods into my Login controller and return some json:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* The user has been authenticated.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function authenticated(Request $request, $user)
{
return response()->json('You have successfully logged in', 200);
}
/**
* The user has logged out of the application.
*
* #param \Illuminate\Http\Request $request
* #return mixed
*/
protected function loggedOut(Request $request)
{
return response()->json('You have logged out now.', 200);
}
}
The problem I am getting when I try to login is the following console.log:
dispatchXhrRequest # app.js:279
xhrAdapter # app.js:118
dispatchRequest # app.js:726
Promise.then (async)
request # app.js:528
Axios.<computed> # app.js:553
wrap # app.js:1071
logout # app.js:2057
invokeWithErrorHandling # app.js:42933
invoker # app.js:43258
original._wrapper # app.js:48611
app.js:2060 Error: Request failed with status code 500
at createError (app.js:653)
at settle (app.js:899)
at XMLHttpRequest.handleLoad (app.js:166)
app.js:279 XHR failed loading: POST "http://localhost:8000/api/logout".
I get that response only some o the time, the rest of the time I get my only view returned as the response from the axios call.
Here are my Laravel web routes:
// Authentication Routes...
Route::post('api/login', 'Auth\LoginController#login');
Route::post('api/logout', 'Auth\LoginController#logout')->name('logout');
// Registration Routes...
Route::post('register', 'Auth\RegisterController#register');
Route::get('{any?}', function () {
return view('index');
// });
// })->where('any', '^(?!api).*$');
})->where('any', '.*');
And here is my Vue router:
{ path: '/login', component: LoginPage },
{ path: '/register', component: RegisterPage },

First add in your form
then submit button to submit and call login method in vuejs method section.In my case
Login
In vuejs component method i put
document.getElementById("login_form").submit();
If you are using Laravel 6, then there will be Auth::routes() in web.php under routes.php. Login and logout request from your will be handled trhough Auth::routes(). There is no need to add routes to vuejs router and handling these requests in api.php.The logout method is called in same way.

Related

Composable for checking Laravel Sanctum's CSRF token in Nuxt 3

I'm working on a Nuxt 3 app with Laravel 9 as API with Sanctum and Fortify.
I wrote a composable to check the response to the Laravel Sanctum's CSRF token HTTP route ('/sanctum/csrf-cookie') and I'm trying to check if it's working by logging via console.log() the composable's state, but it always shows an empty Proxy received as error.
Please help, what am I doing wrong?
Here is what I've done so far:
The composable: auth.ts :
export function useCsrfToken() {
const baseURL = useRuntimeConfig().public.LARAVEL_BASE_URL
const options = {
baseURL,
credentials: 'include',
headers: {
Accept: 'application/json',
} as HeadersInit,
method: 'GET',
}
const state = ref({
status: '',
error: {},
})
async function getCsrfToken() {
state.value.error = {}
state.value.status = ''
await $fetch('/sanctum/csrf-cookie', options)
.then(() => (state.value.status = 'ok'))
.catch((error) => {
console.log('error from useCsrfToken', error)
state.value.error = { error }
})
}
getCsrfToken()
return { state }
}
The login Nuxt page, where I'm using the above composable :
<script setup>
definePageMeta({
layout: false,
})
const submit = () => {
form.processing = true
const { state } = useCsrfToken()
console.log('error from login', state.value.error)
// console.log('status from login', status)
}
const form = reactive({
email: '',
password: '',
remember: false,
processing: false,
errors: {},
})
</script>
<template>
<div>
<!-- <TheTwoFactorChallenge v-if="verification" #2fapassed="submit" /> -->
<NuxtLayout name="auth">
<template #title>
<p class="card-header-title">Inicie Sesión</p>
</template>
<form novalidate #submit.prevent="submit">
<div class="field">
<label class="label" for="email">Usuario</label>
<div class="control has-icons-left has-icons-right">
<AppInput
type="email"
id="email"
v-model="form.email"
:error="form.errors?.email"
autocomplete="email"
required
/>
<AppIconLeft icon="fa-solid fa-envelope" />
<AppIconError v-if="form.errors?.email" class="is-right" />
</div>
<AppHelpError :errors="form.errors?.email" />
</div>
<div class="field">
<label class="label" for="password">Contraseña</label>
<div class="control has-icons-left has-icons-right">
<AppInput
type="password"
id="password"
v-model="form.password"
:error="form.errors?.password"
autocomplete="new-password"
required
/>
<AppIconLeft icon="fa-solid fa-lock" />
<AppIconError v-if="form.errors?.password" class="is-right" />
</div>
<AppHelpError :errors="form.errors?.password" />
</div>
<div class="field">
<div class="control">
<AppSwitch
id="remember"
v-model:checked="form.remember"
class="is-small is-link"
/>
<label for="remember">Recuérdame</label>
</div>
</div>
<div class="is-flex is-justify-content-flex-end mb-4">
<NuxtLink to="#" class="has-text-link">
¿Olvidó su contraseña?
</NuxtLink>
</div>
<div class="field">
<div class="control">
<AppButton
class="is-link is-fullwidth"
type="submit"
:is-loading="form.processing"
>Entrar</AppButton
>
</div>
</div>
</form>
</NuxtLayout>
</div>
</template>
And here are the outputs :
Since Nuxt3 provides a Server function which could use as an API, it could be easily confused with Laravel.
You may need to add a proxy_pass (if you use Nginx) to distinguish between Nuxt and Laravel requests in order to share the same origin between Nuxt and Laravel (to avoid wasting a second on Same-Origin-Policy )
For example:
http://example.com/ // -> nuxt
http://example.com/api/ // -> laravel
(Since you said you are using Docker, I'm going to assume you're using laravel sail)
First, add a domain (for example http://example.test) for your site instead of http://localhost.
Custom the /sanctum/csrf-cookie route to /api/sanctum/csrf-cookie (here you can see how to change laravel sanctum csrf cookie route)
Add a proxy_pass for http://example.test/api/* in Nginx configs:
location ~* ^/(api|broadcasting|storage)/ {
proxy_pass http://localhost:8080; // where your laravel running
}
But, in my view, the SSR Nuxt should be considered as a client just equal to an Android app or IOS app, because the frontend and the backend are entire two different projects compared with the previous time when putting your frontend code and backend code in the same project.
So, you may auth the web just like auth the Android app, instead of booting the CRSF protection.
And here is a full example of Nuxt3 + Laravel, which is using Laravel Sanctum and also SSR with authorizations. 👉 Laravel + SSR Nuxt3 with authorizations

ASP.NET Core Razor Page, code behind method not being triggered

I have a C# Razor Pages project.
I created a Login view in the following structure:
- Pages
- Account
- Login.cshtml
This is the code for my Login view
#page "{handler?}"
#model HAL_WEB.Pages.LoginModel
#{
Layout = "_LayoutLogin";
}
<section class="section register min-vh-100 d-flex flex-column align-items-center justify-content-center py-4">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-4 col-md-6 d-flex flex-column align-items-center justify-content-center">
<div class="d-flex justify-content-center py-4">
<a href="index.html" class="logo d-flex align-items-center w-auto">
<img src="assets/img/teamtruetech_logo.png" alt="">
<span class="d-none d-lg-block">HAL Admin</span>
</a>
</div><!-- End Logo -->
<div class="card mb-3">
<div class="card-body">
<div class="pt-4 pb-2">
<h5 class="card-title text-center pb-0 fs-4">Login to Your Account</h5>
<p class="text-center small">Enter your username & password to login</p>
</div>
<form id="login-form" class="row g-3 needs-validation" novalidate>
<div class="col-12">
<label for="yourUsername" class="form-label">Username</label>
<div class="input-group has-validation">
<span class="input-group-text" id="inputGroupPrepend"></span>
<input type="text" name="username" class="form-control" id="yourUsername" required>
<div class="invalid-feedback">Please enter your username.</div>
</div>
</div>
<div class="col-12">
<label for="yourPassword" class="form-label">Password</label>
<input type="password" name="password" class="form-control" id="yourPassword" required>
<div class="invalid-feedback">Please enter your password!</div>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" value="true" id="rememberMe">
<label class="form-check-label" for="rememberMe">Remember me</label>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary w-100" type="submit">Login</button>
</div>
#* <div class="col-12">
<p class="small mb-0">Don't have account? Create an account</p>
</div>*#
</form>
</div>
</div>
</div>
</div>
</div>
</section>
#section Scripts {
<script src="~/assets/js/loginpage.js"></script>
}
And this is the code behind:
using HAL_WEB.Data;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Security.Claims;
namespace HAL_WEB.Pages
{
public class LoginModel : PageModel
{
private readonly ApplicationDBContext _dbContext;
public LoginModel([FromServices] ApplicationDBContext dbContext)
{
_dbContext = dbContext;
}
public void OnGet()
{
}
public async Task<IActionResult> OnPostLoginAsync(string username, string password)
{
// Check if the provided credentials are valid
if (IsValidCredentials(username, password))
{
// If the credentials are valid, log the user in
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }, CookieAuthenticationDefaults.AuthenticationScheme)),
new AuthenticationProperties
{
IsPersistent = true, // Set this to true if you want the user to stay logged in after closing the browser
ExpiresUtc = DateTime.UtcNow.AddDays(7) // Set the expiration time for the cookie
});
// Redirect the user to the home page
return RedirectToPage("/Home");
}
else
{
// If the credentials are invalid, show an error message
ModelState.AddModelError(string.Empty, "Invalid username or password.");
return Page();
}
}
private bool IsValidCredentials(string username, string password)
{
// Replace this with your own validation logic
return username == "admin" && password == "password";
}
public IActionResult OnPostLoginTestAsync()
{
return new JsonResult(true);
}
}
In my Javascript file I tried to call the method OnPostLoginTestAsync or OnPostLoginAsync without success.
I'm getting a "Bad Request 400" error:
This is my Javascript Axios code for calling the method:
// Use Axios to send a POST request to the server with the form data
axios.post('/Account/Login?handler=login', {
username,
password,
})
.then((response) => {
// If the request is successful, redirect the page
window.location.href = '/home';
})
.catch((error) => {
// If there is an error, log it to the console
console.error(error);
});
Any clue what am I doing wrong? I'm going to /Account/Login?handler=login because the call is a Post and what I think is that the method OnPostLoginAsync should be executed.
UPDATE
I found something interesting, I created the following Get method:
public IActionResult OnGetTestAsync()
{
return new JsonResult(true);
}
And in my Javascript, I changed the Axios url to be:
axios.get('/Account/Login?handler=test')
.then(function (response) {
})
.catch(function (error) {
// Handle the error response
});
And I could get the method executed! But when I change the method name back to:
OnPostTestAsync
and my Axios to:
axios.post('/Account/Login?handler=test')
.then(function (response) {
})
.catch(function (error) {
// Handle the error response
});
It never gets executed and I get 400 Bad Request. Any clue?

Vee-Validate Showing a BlankPage when doing the configurations

I using Vue3.0 and also use Vee-validate to validate my form. But I don't know why it keep showing blank page just like in this picture
I already do what they said in the documentations, here's my code
<form class="wrap" id="signup-form col-lg-5" #submit.prevent="processForm">
<div class="row mb-5">
<router-link :to="{'name': 'Home'}">
<span class="iconify" data-icon="ion:return-up-back-outline" data-width="25" data-height="25"></span>
<button class="btn">Back</button>
</router-link>
</div>
<!-- full name -->
<div class="form-group row">
<label for="name">Full Name <span class="text-danger">*</span></label>
<ValidationProvider rules="positive|odd" v-slot="err">
<input type="text" class="form-control" v-model.trim="name">
<span>{{ err.errors[0] }}</span>
</ValidationProvider>
</div>
<!-- submit button -->
<div class="row d-flex align-items-center">
<button type="submit" class="btn btn-outline-dark col-sm-4">Submit</button>
<p style="cursor:pointer;" class="col-sm-7">
<router-link :to="{name:'Login'}">Has an account? Login</router-link>
</p>
</div>
</form>
And here's my script
<script>
import { ValidationProvider } from 'vee-validate';
import { extend } from 'vee-validate';
extend('odd', value => {
return value % 2 !== 0;
});
extend('positive', value => {
return value >= 0;
});
export default {
name: 'RegisterForm',
components: {
ValidationProvider
},
data: function(){
return{
name: '',
}
},
methods: {
processForm() {
this.$emit('form-submit',
{
'name': this.email,
'password': this.password,
})
}
}
}
</script>
What should I change from the code? By the way, Is Vee-Validator should be assigned in main.js?
Looks like you are using vee-validate v3.x, it isn't compatible with Vue 3
vee-validate v4 was released recently that supports Vue 3, but with a completely different API
https://vee-validate.logaretm.com/v4/

How can I create role based testing in Cypress?

I have a project that needs to be tested via Cypress. I am new in "Cypressing" by the way..
I have problem to find a solution about role-based testing where something like this happen:
If the xhr response gave a user data with role admin, it will redirect to dashboard/admin. Otherwise, if the xhr response gave a user data a role user, it will redirect to dashboard/user.
After that, each views may have different actions & behavior regarding to what kind of user who has logged in.
Please have a look at my script:
const BASE_URL = Cypress.env('BASE_URL')
const APP_NAME = Cypress.env('APP_NAME')
const AUTH_ID = Cypress.env('AUTH_ID')
const PASSWORD = Cypress.env('PASSWORD')
describe('Login Test', () => {
it('can perform a login action', () => {
cy.visit(BASE_URL)
cy.contains(APP_NAME)
cy.get('input[name="contact"]')
.type(AUTH_ID)
.should('have.value', AUTH_ID)
cy.get('input[name="password"]')
.type(PASSWORD)
.should('have.value', PASSWORD)
cy.get('#submit-button').click()
cy.url().should('contain', 'dashboard')
})
})
and please have a look at my Vue.js script too:
<template>
<div id="login">
<div class="login-card p-4">
<h1 class="text-center font-weight-bold text-primary py-5">STTKD</h1>
<!-- phone number input -->
<div class="form-group">
<label for="contact">Nomor Telepon</label>
<input
name="contact"
type="number"
class="form-control"
min="0"
placeholder="08xxx atau 628xxxx"
v-model="user.contact"
/>
</div>
<!-- password-input -->
<div class="form-group">
<label for="password">Password</label>
<input
name="password"
type="password"
class="form-control"
placeholder="Masukkan password"
v-model="user.password"
/>
</div>
<div class="form-group text-right">
Forgot password?
</div>
<!-- login-button -->
<div class="form-group">
<button
id="submit-button"
type="button"
class="btn btn-primary btn-block"
#click="onSubmit"
>LOGIN</button>
</div>
<div class="form-group text-center">
<p class="mb-0">
Don't have account?
<router-link :to="{ name: 'register' }" class="text-primary">Create a new one!</router-link>
</p>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
user: {
contact: "",
password: ""
}
};
},
methods: {
onSubmit() {
axios
.post("auth/login", this.user)
.then(resp => {
VueCookies.set("access_token", resp.data.access_token);
VueCookies.set("token_type", resp.data.token_type);
VueCookies.set("expires_at", resp.data.expires_at);
// redirect with" user" param if the roles array from the response includes "user"
if (resp.data.user.roles.includes("user")) {
this.$router.replace({
name: "dashboard",
params: {
role: "user"
}
});
return;
}
// else, redirect with "admin" param
this.$router.replace({
name: "dashboard",
params: {
role: "admin"
}
});
})
.catch(err => console.error(err));
}
}
};
</script>
As you see above, its running without problem but I have no idea about what to do next because the user behavior is kindof dynamic.
Please help me to solve this problem, any references are good for me if there is.
Thanks in advance.

Cannot read property 'post' of undefined, in nuxt js

I am making registration form in nuxt js, it takes data from api, I have installed axios and auth module, I wrote base url in nuxt.config.js file. It shows TypeError: Cannot read property 'post' of undefined
```template>
<div>
<section class="content">
<div class="register_form m-auto text-center form-group">
<form method="post" #submit.prevent="register" >
<h1 class ="register_title">REGISTER</h1>
<h2 class="register_text">PLEASE REGISTER TO USE THIS WEBSITE</h2>
<input class="form-control" type="text" placeholder = 'USERNAME' v-model="username" name="username" required>
<input class="form-control" type="password" placeholder = 'PASSWORD' v-model="password" name="password" required>
<button type="submit" to="#" class="register_btn">
REGISTER
</button>
</form>
</div>
</section>
</div>
</template>
<script>
export default {
layout: 'loginLayout',
data(){
return {
username: '',
password: ''
}
},
methods: {
async register() {
try {
await this.$axios.post('register', {
username: this.username,
password: this.password
})
this.$router.push('/')
}
catch (e) {
console.log(e)
}
}
}
}
</script>```
try to use
await this.$axios.$post instead of await this.$axios.$post