Laravel Sanctum + Vue Auth - laravel

I have an API that is secured using Laravel Sanctum. In the front-end I managed to login with sanctum and a new bearer token is issued. So far so good but how can I store the token (without Vuex) to local storage and how I tell axios to use that token for any future requests ?
My code is as follows:
// AuthController.php
public function login(Request $request)
{
$fields = $request->validate([
'email' => 'required|string',
'password' => 'required|string'
]);
$user = User::where('email', $fields['email'])->first();
$token = $user->createToken('login_token')->plainTextToken;
if (!$user || !Hash::check($fields['password'], $user->password)) {
return response([
'message' => 'Invalid Login Credentials'
], 401);
}
$response = [
'user' => $user,
'token' => $token
];
return response($response, 201);
}
// Login.vue
export default {
methods: {
handleLogin() {
axios.get("/sanctum/csrf-cookie").then(response => {
axios.post("/api/login", this.formData).then(response => {
if (response.status === 201) {
this.$router.push({ path: "/" });
}
console.log(response);
});
});
}
},
data: function() {
return {
logo: logo,
formData: {
email: "",
password: ""
}
};
}
};
The login works fine, it is returning the expected results but I don't know how to store the generated token and how to send it to any future axios requests.
Thanks.
*** UPDATE ***
Ok so I figured it out how to send the headers and I'm doing it like so:
axios
.get("/api/expenses/", {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + "6|cnuOY69grhJiZzxEHoU74PapFkkcJeqbf3UiKx8z"
}
})
.then(response => (this.expenses = response.data.data));
}
The only remaining bit to this is how can I store the token to the local storage because right now, for testing purposes I hard coded the token.

In your Login.vue script
methods:{
loginUser(){
axios.post('/login',this.form).then(response =>{
localStorage.setItem('token', response.data.token) //store them from response
this.alerts = true
this.$router.push({ name: 'Dashboard'})
}).catch((errors) => {
this.errors = errors.response.data.errors
})
},
}
In your logged in file lets call it Dashboard.vue in <script>,
data(){
return {
drawer: true,
user: {roles: [0]},
token: localStorage.getItem('token'), //get your local storage data
isLoading: true,
}
},
methods: {
getUser(){
axios.get('/user').then(response => {
this.currentUser = response.data
this.user = this.currentUser.user
}).catch(errors => {
if (errors.response.status === 401) {
localStorage.removeItem('token')
this.$router.push({ name: 'Login'})
}
}).finally(() => {
setTimeout(function () {
this.isLoading = false
}.bind(this), 1000);
})
},
},
created(){
axios.defaults.headers.common['Authorization'] = `Bearer ${this.token}` //include this in your created function
this.getUser()
this.isCreated = true
}
I hope it works well for you

Ok so this is how I sorted this out.
Storing the token in the local storage:
localStorage.setItem("token", response.data.token);
Using the token when sending an axios call to the api:
const token = localStorage.getItem("token");
axios
.get("/api/endpoint/", {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token
}
})
.then(response => (this.expenses = response.data));
}

Related

laravel sanctum returns "unauthorized" while trying to logout other than dashboard

I have developed a laravel-vuejs app, implememted sanctum auth system here. While I attempt to logout from my dashboard It works nicely, but while I am attempting logout from landing page, it returns "401 unauthorized" status.
I am sharing my logout function here:
logout() {
axios.defaults.headers.common['Authorization'] = `Bearer ${this.token}`
axios.post('/api/logout').then(response => {
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
this.$router.push('/login')
}).catch(errors => {
console.log(errors.response.data)
})
}
Code from my api.php
Route::post('/logout','App\Http\Controllers\Authentication#logout')-
>middleware('auth:sanctum');
from router.js
const routes = [
{
path : '/',
name : 'home',
component : home,
meta : { layout: website }
},
]
var router = createRouter({
history : createWebHistory(process.env.BASE_URL),
routes
})
function loggedIn() {
return localStorage.getItem('token')
}
router.beforeEach((to, from, next) => {
if(to.matched.some(record => record.meta.requiresAuth)) {
if(!loggedIn()) {
next({
name : 'login'
})
}else{
next()
}
}else if(to.matched.some(record => record.meta.isVisitor)) {
if(loggedIn()) {
next({
name : 'dashboard'
})
}else{
next()
}
}else{
next()
}
})

how can I customize json responses of laravel api and show them in vuex

Trying different solutions, I am fooling around with
response()->json([ ])
To create responses that I can read in my vue / vuex application
The Laravel api function that stores a new Speler ( dutch for player ;)):
I have trouble sending the created, or found Speler-object, through the response to the vuex-store.
Tried to set the status to 202 when succesfully logged, yet the actual status sent is 200..
It is clear that I do not understand it well enough. Can anyone help and explain?
public function store(Request $request)
{
if (Game::where('id',$request['game_id'])->exists() ){
if (!Speler::where('name',$request['name'])->where('game_id',$request['game_id'])->exists()){
$newSpeler = Speler::create(
[
'name' => $request['name'],
'pass_code' => $request['pass_code'],
'game_id' => $request['game_id']
])->first());
return $newSpeler;
}
elseif ( Speler::where('name',$request['name'])->where('game_id',$request['game_id'])->where('pass_code', $request['pass_code'])->exists()){
$speler = Speler::where('name',$request['name'])->where('game_id',$request['game_id'])->where('pass_code', $request['pass_code']);
return response()->json(['speler'=> $speler, 202]);
}
return response()->json(['status' => 'This name is already used, pass-code is not correct', 409]);
}
return response()->json([ 'status' => 'The game-pin does not exist', 403 ]);
}
This is called form the vuex actions:
export const addSpeler = ({commit}, formData) => {
return new Promise((resolve, reject) => {
fetch(`api/speler`, {
method: 'post',
body:formData,
})
.then(res => {
if (res.status === 202){
resolve('De speler is succesfully logged on');
commit('SET_CURRENT_SPELER', res.data.speler);
}
else if (res.status === 201){
commit('SET_CURRENT_SPELER', res.data);
resolve('De speler is succesfully added')
}
else {
reject('De speler is not logged in. Name exists and does not match passcode');
}
})
.catch(err => {
reject(err.message)
});
})
}
and this is called from a vue method:
methods: {
addSpeler(){
this.errorMessage ='';
this.spelerAdded =false;
const formData = new FormData();
formData.append('name', this.name);
formData.append('pass_code',this.pass_code);
formData.append('game_id', this.currentGame.id);
this.$store.dispatch('addSpeler', formData )
.then(res => {
this.spelerAdded = true;
console.log(res.status);
})
.catch(err => {
this.errorMessage = err;
this.spelerAdded = false;
});
},
mutations.js:
export const SET_CURRENT_SPELER = (state, speler) => {
state.currentSpeler = speler;
}
state.js:
export default{
currentGame:{},
currentSpeler:{}
}
The comment by porloscerros answered the question perfectly :
the status goes as the second argument of the json method return response()->json(['speler'=> $speler], 202); (and not inside the array as you are doing). If you don't pass a second argument, the argument value is assigned to 200 by default json(mixed $data = [], int $status = 200, array $headers = [], int $options = 0)

login from nuxtjs to laravel/passport server

I made api's using laravel, I am using laravel passport for authentication I want to send login request from nuxtjs to laravel backend but I'm getting 401 (Unauthorized) with any credential from the database.
nuxt.config.js
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/login', method: 'post', propertyName: 'token' },
user: { url: '/user', method: 'get' }
},
clientId: '1',
clientSecret: 'clientsecret'
}
}
},
modules: [
'#nuxtjs/axios',
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
'#nuxtjs/auth',
],
axios: {
baseURL: "http://prostudent.test/api"
},
login.vue
<template>
Login
</template>
<script>
export default {
auth: false,
email: "vuejs#gmail.com",
password:"123456",
methods: {
async login() {
try {
const data = { email: this.email, password: this.password }
await this.$auth.loginWith('local', { data: data })
} catch (e) {
}
}
}
}
</script>
login function in AuthController.php
public function login(Request $request){
try {
if (Auth::attempt($request->only('email', 'password'))) {
/** #var User $user */
$user = Auth::user();
$token = $user->createToken('app')->accessToken;
return response([
'message' => 'success',
'token' => $token,
'user' => $user
]);
}
}catch (\Exception $exception){
return response([
'message' =>$exception->getMessage()
], 400);
}
return response([
'message'=> 'invalid username/password'
], 401);
ps: I tried to do it like its mentioned here which says in nuxt.js set up "You will need to copy the .env.example file to .env and populate it with values from laravel." but I dont have .env file in my nuxtjs project should I create it manually? otherwise I noticed that he didn't do anything extra on the serverside to connect it with nuxtjs, all the work is done on nuxtjs side.
please debug to see if it is the last return line that is being returned or something else.
if it's not that line being returned, it might be the issue from Auth/Nuxt property and it's failing to fetch user data without token property.
as far as I know, in newer version of Nuxt/Auth, you should define properties seperately and not in endpoint. try codes below and see if it helps.
strategies: {
local: {
token: {
property: 'token',
global: true,
},
user: {
property: 'user',
},
endpoints: {
login: { url: '/login', method: 'post'},
user: { url: '/user', method: 'get' }
}
}
}

Fetch in react native doesn't send the post with codeIgniter API

Fetch post is not working with the json api coded with codeIgniter. The get method works but there's issue in post method. The key is not recognized between the react native and code igniter. Any help is appreciated. Thankyou
fetch('http://zzz.com/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'abc',
}),
})
.then(response => response.json())
.then(responseJson => {
console.log('responseJson', responseJson);
})
.catch((error) => {
console.error('fetchError', error);
});
CodeIgniter controller
public function login()
{
$un = $this->input->post('username'); //why doesn't the key 'username' is working here
echo json_encode($un);
}
CONSOLE LOG:
responseJson false
Updates:
1)Using json_decode, gives error
"fetchError SyntaxError: Unexpected end of JSON input"
public function login()
{
$Data = json_decode(file_get_contents('php://input'), false);
echo $Data;
}
2)Using json_encode gives following outcomes:
responseJson {"username":"abc"}
public function login()
{
$Data = json_encode(file_get_contents('php://input'), false);
echo $Data;
}
Update 1:
1) Using only file_get_contents gives the output as: responseJson {username: "abc"}
public function login()
{
$Data = (file_get_contents('php://input'));
echo $Data;
}
2)using var_dump and json_decode in server code, it gives following error in the app console
public function login()
{
$Data = json_decode(file_get_contents('php://input'), true);
var_dump ($Data);
}
Error:
fetchError SyntaxError: Unexpected token a in JSON at position 0
at parse (<anonymous>)
at tryCallOne (E:\zzz\node_modules\promise\setimmediate\core.js:37)
at E:\zzz\node_modules\promise\setimmediate\core.js:123
at E:\zzz\node_modules\react-native\Libraries\Core\Timers\JSTimers.js:295
at _callTimer (E:\zzz\node_modules\react-native\Libraries\Core\Timers\JSTimers.js:152)
at _callImmediatesPass (E:\zzz\node_modules\react-native\Libraries\Core\Timers\JSTimers.js:200)
at Object.callImmediates (E:\zzz\node_modules\react-native\Libraries\Core\Timers\JSTimers.js:464)
at MessageQueue.__callImmediates (E:\zzz\node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:320)
at E:\zzz\node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:135
at MessageQueue.__guard (E:\zzz\node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:297)
console log the response as following gives the array in app console:
fetch('http://zzz.com/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'abc',
})
})
.then((response) => console.log('response', response))
Console:
response
Response {type: "default", status: 200, ok: true, statusText: undefined, headers: Headers, …}
headers:Headers {map: {…}}
ok:true
status:200
statusText:undefined
type:"default"
url:"http://zzz.com/login"
_bodyInit:"array(1) {↵ ["username"]=>↵ string(3) "abc"↵}↵"
_bodyText:"array(1) {↵ ["username"]=>↵ string(3) "abc"↵}↵"
__proto__:Object
Try to add same-origin mode like below :
fetch('http://zzz.com/login', {
method: 'POST',
credentials: 'same-origin',
mode: 'same-origin',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'abc',
}),
})
.then(response => response.json())
.then(responseJson => {
console.log('responseJson', responseJson);
})
.catch((error) => {
console.error('fetchError', error);
});
From the front end, send the data as a form data (as shown below).
const formData = new FormData();
formData.append("name", "RandomData");
formData.append("operation", "randomOperation");
In your Codeigniter controller, receive it as...
$name = $this->input->post("name");
$operation = $this->input->post("operation");

Laravel passport & Vue login

I've made login function in laravel with passport api and I'm getting status 200, the issue is that i don't know how to login the user and redirect to homepage after this successful request (I'm using vuejs component).
Code
controller
public function login(Request $request)
{
$credentials = [
'email' => $request->email,
'password' => $request->password
];
if (Auth::attempt($credentials)) {
// $token = auth()->user()->createToken('AppName')->accessToken;
// $success['token'] = $token;
// $success['name'] = auth()->user()->name;
// $success['id'] = auth()->user()->id;
$user = Auth::user();
$success['token'] = $user->createToken('AppName')->accessToken;
$success['user'] = $user;
return response()->json(['success'=>$success], 200);
} else {
return response()->json(['error' => 'UnAuthorised'], 401);
}
}
component script
<script>
import {login} from '../../helpers/Auth';
export default {
name: "login",
data() {
return {
form: {
email: '',
password: ''
},
error: null
};
},
methods: {
authenticate() {
this.$store.dispatch('login');
axios.post('/api/auth/login', this.form)
.then((response) => {
setAuthorization(response.data.access_token);
res(response.data);
})
.catch((err) =>{
rej("Wrong email or password");
})
}
},
computed: {
authError() {
return this.$store.getters.authError;
}
}
}
</script>
Auth.js (imported in script above)
import { setAuthorization } from "./general";
export function login(credentials) {
return new Promise((res, rej) => {
axios.post('/api/auth/login', credentials)
.then((response) => {
setAuthorization(response.data.access_token);
res(response.data);
})
.catch((err) =>{
rej("Wrong email or password");
})
})
}
general.js (imported in script above)
export function setAuthorization(token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`
}
Question
How can I login my user after successful request?
...........................................................................................................................................................
Say that you have defined a vuex auth module with a login action
that accepts a credentials object.
If success it receives a response that contains the access_token our API granted to the user.
We store/commit the token and also update axios settings to use that token on each of the following requests we make.
import axios from 'axios';
const state = {
accessToken: null,
};
const mutations = {
setAccessToken: (state, value) => {
state.accessToken = value;
},
};
const getters = {
isAuthenticated: (state) => {
return state.accessToken !== null;
},
};
const actions = {
/**
* Login a user
*
* #param context {Object}
* #param credentials {Object} User credentials
* #param credentials.email {string} User email
* #param credentials.password {string} User password
*/
login(context, credentials) {
return axios.post('/api/login', credentials)
.then((response) => {
// retrieve access token
const { access_token: accessToken } = response.data;
// commit it
context.commit('setAccessToken', accessToken);
return Promise.resolve();
})
.catch((error) => Promise.reject(error.response));
},
};
Before every request to our API we need to send the token we received and store on our auth module therefore we define a global axios request interceptor on our main.js
import store from '#/store';
...
axios.interceptors.request.use(
(requestConfig) => {
if (store.getters['auth/isAuthenticated']) {
requestConfig.headers.Authorization = `Bearer ${store.state.auth.accessToken}`;
}
return requestConfig;
},
(requestError) => Promise.reject(requestError),
);
...
We then define our login component which on a success login redirects us to the dashboard page
<template>
<div>
...
<form #submit.prevent="submit">
...
<button>Submit</button>
</form>
...
</div>
</template>
<script>
import { mapActions } from 'vuex';
export default {
data() {
return {
credentials: {
email: '',
password: '',
},
};
},
methods: {
...mapActions('auth', [
'login',
]),
submit() {
this.login({ ...this.credentials })
.then(() => {
this.$router.replace('/dashboard');
})
.catch((errors) => {
// Handle Errors
});
},
},
}
Finally we define our routes and their guards
import store from '#/store'
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'landing',
component: Landing,
// User MUST NOT BE authenticated
beforeEnter: (to, from, next) => {
const isAuthenticated = store.getters['auth/isAuthenticated'];
if (isAuthenticated) {
return next({
name: 'dashboard',
});
}
return next();
},
},
{
path: '/login',
name: 'login',
component: Login,
// User MUST NOT BE authenticated
beforeEnter: (to, from, next) => {
const isAuthenticated = store.getters['auth/isAuthenticated'];
if (isAuthenticated) {
return next({
name: 'dashboard',
});
}
return next();
},
},
{
path: '/dashboard',
name: 'dashboard',
component: Dashboard,
// User MUST BE authenticated
beforeEnter: (to, from, next) => {
const isAuthenticated = store.getters['auth/isAuthenticated'];
if (!isAuthenticated) {
return next({
name: 'login',
});
}
return next();
},
},
{ path: '*', redirect: '/' },
],
});
Now only users with an access token can have access to dashboard route and any child routes you may define in the future. (No further check is necessary as any child of this route will execute that guard).
If someone attempts to access dashboard route without an access token will be redirected to login page
If someone attempts to access landing or login page with an access token will be redirected back to dashboard.
Now what happens if on any of our future API requests our token is invalid?
we add a global axios response interceptor on our main.js and whenever we receive a 401 unathorized response we clear our current token and redirect to login page
import store from '#/store';
...
axios.interceptors.response.use(
response => response,
(error) => {
if (error.response.status === 401) {
// Clear token and redirect
store.commit('auth/setAccessToken', null);
window.location.replace(`${window.location.origin}/login`);
}
return Promise.reject(error);
},
);
...
Final Words
I believe that all of the above steps are enough to help you have a better understanding on how to use the access token. Of course you should also store the token on browsers localStorage so that the user doesnot have to login whenever experiences a page refresh and token gets clear from memory. And at least refactor router beforeEnter functions by moving them to a separate file to avoid repetition.

Resources