So I am writing a frontend project in VueJS. I already have a custom express backend serving an API. It's not necessarily an issue, but when I use axios, the cookies are being passed with ever request; even if I set 'withCredentials: false' and by default.
<template>
<div>
<h1>Login Vue</h1>
<input
type="text"
name="username"
v-model="input.username"
placeholder="Username"
/>
<input
type="password"
name="password"
v-model="input.password"
placeholder="Password"
/>
<button type="button" v-on:click="login()">Login</button>
<button type="button" v-on:click="getMe()">GetMe</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'HelloWorld',
props: {
msg: String,
},
data: function() {
return {
input: {
username: '',
password: '',
},
};
},
methods: {
login: async function() {
const user = await axios.post(
`/api/v1/users/login`,
{
username: this.input.username,
password: this.input.password,
},
{ withCredentials: true }
);
console.log(user);
},
getMe: async function() {
const me = await axios.get(`/api/v1/users/me`, {
withCredentials: false,
});
console.log(me);
},
},
};
</script>
You can see the two async methods; the 'getMe' method will still send cookies to the backend even if set false. The cookie is set from the backend on login, it's a httpOnly cookie with an JSON token in it for backend authentication. Obviously in the real world, I would want to send credentials; but I noticed it was sending cookies by default and when told false.
Is there something I am missing? If I use the Fetch() API and use "credentials: 'omit'" the cookies are not sent to the backend.
This is a brand new clean, VueJS 2 project created from the CLI. The only 'special' thing I am doing it a custom proxy so requests are proxied to the backend.
// vue.config.js;
module.exports = {
devServer: {
proxy: {
'/': {
target: 'http://localhost:3010',
},
},
},
};
If I console.log req.cookies in the backend on the GET request i get:
{
authToken: 'RANDOM JSON TOKEN'
}
withCredentials only applies to cross-origin requests (which have to ask for explicit permission (per the CORS specification) before including cookies).
Try configuring the client upfront explicitly, by setting "axios.defaults.withCredentials = false", instead of when making a call.
Also, i recall hearing that some JS frameworks overwrite this setting anyway, when assembling XMLHttpRequest object.
Related
I am trying to implement JWT-based user sessions with SvelteKit, and have mostly been following the explanation for form actions given on their website: https://kit.svelte.dev/docs/form-actions
+page.svelte
<form method="POST" action="?/signIn">
<input type="text" name="name" />
<input type="password" name="password" />
<button type="submit">Submit</button>
</form>
+page.server.svelte
import { fail, redirect } from "#sveltejs/kit";
import { signIn } from "$lib/server/database";
export const actions = {
signIn: async ({ cookies, request }) => {
const data = await request.formData();
const name = data.get("name");
const password = data.get("password");
if (!name || !password) {
return fail(400);
}
try {
cookies.set("jwt", await signIn(name, password));
} catch (error) {
return fail(400);
}
throw redirect(303, "/");
},
};
I have tested my signIn method which I import and use here, and it does return a token when called with the correct credentials. So far, so good. However, I noticed that I don't see any cookies in my developer tools. It seems like the cookies.set() call simply does nothing. I'd like to set the returned JWT as a cookie so that I can authenticate my users, so what am I doing wrong?
In case anybody else has this problem: While the cookie was set as it was supposed to when using Chrome, it wasn't in Safari. I solved this by setting the secure option to false, even though the SvelteKit docs state that this is done automatically on localhost.
I am trying to create a cookie in svelte (and I am also using svelte kit) and access it. I am want to use the cookie for authentication purposes, more specifically, to store a JWT Token.
I am tried implementing pure JS code, such as getCookie() and setCookie() as shown here W3Schools - JavaScript Cookies. But I can't get access to the document. I have also tried serialize from the cookie npm package, as shown below, and I have also tried using browser as shown below.
import { serialize } from "cookie";
import { browser } from '$app/environment';
You can e.g. set a cookie in a form action. If you want to be able to read it in the browser, you have to disable HttpOnly (in general you should avoid this, as it makes cross site scripting vulnerabilities worse).
A simple example:
<!-- +page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
export let form: { error?: string; } | null;
</script>
<form method="post" use:enhance>
<label>Login <input type="text" name="login" /></label>
<label>Password <input type="password" name="password" /></label>
{#if form?.error}<p>{form.error}</p>{/if}
<button type="submit">Login</button>
</form>
// +page.server.ts
import { invalid, redirect } from '#sveltejs/kit';
import type { Actions } from './$types';
export const actions: Actions = {
default: async ({ request, cookies }) => {
const formData = await request.formData();
const login = formData.get('login');
const password = formData.get('password');
if (login == 'admin' && password == '...') {
cookies.set(
'auth', '42',
{
path: '/',
maxAge: 60 * 60 * 24 * 365,
httpOnly: false, // <-- if you want to read it in the browser
},
);
throw redirect(302, '/');
}
return invalid(400, { error: 'Invalid login or password' });
},
}
The cookie can be read from document.cookie, note that this will throw an error during SSR, so you have to check browser or read it in onMount.
I'm struggling with the configuration of auth + Axios.
I'm currently working on our social login (FB and google). It half works.
First of all, I have my Axios instance configured as a plugin. We do have two instances, one that we use for general API requests and another one that we will use for logged user requests.
plugins/axios.js
export default function({ $axios, redirect, app }) {
$axios.defaults.baseURL = process.env.baseUrl
$axios.defaults.headers = {
Authorization: `Bearer ${process.env.APIKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
lang: app.i18n.locale
}
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
if (code === 404) {
redirect('/404')
}
if (code === 500) {
redirect('/500')
}
})
}
plugins/auth-axios.js
export default function({ $axios }, inject) {
const authAxios = $axios.create()
// authAxios.setToken('123', 'Bearer')
inject('authAxios', authAxios)
}
The first client works perfectly in the whole app.
Now I'm configuring the auth plugin. When I log in through Facebook or google I need to take the response data of the logged user from the social network and send it to my API, expecting a user with the token as the response. This will be the user that I'll set in the auth plugin. Every time I log in with Facebook it appears to work well except when It arrives at the plugin code. $auth.loggedIn is always false.
I've made it work forcing $auth.fetchUser at the beginning of the code but it doesn't work well at all. It shows an Axios error when console.log($auth). This is my auth code:
plugins/auth.js
export default async function({ app: { $auth, $axios } }) {
$auth.fetchUser()
console.log($auth)
if (!$auth.loggedIn) {
return
}
const authStrategy = $auth.strategy.name
if (authStrategy === 'facebook' || authStrategy === 'google') {
if ($auth.user.google_id || $auth.user.fb_id) return
try {
const url = `/client/social`
var postData
if (authStrategy == 'facebook')
postData = {
name: $auth.user.name,
email: $auth.user.email,
fb_id: $auth.user.id,
avatar: $auth.user.picture.data.url,
birthday: $auth.user.birthday
}
else
postData = {
name: $auth.user.given_name,
surname: $auth.user.family_name,
email: $auth.user.email,
google_id: $auth.user.sub,
avatar: $auth.user.picture,
locale: $auth.user.locale
}
const { data } = await $axios.post(url, postData)
$auth.setToken('client', data.access_token)
$auth.setUser(data.client)
} catch (e) {
console.log(e)
}
}
}
The console.log($auth) error:
'$state': {
user: null,
loggedIn: false,
strategy: 'facebook'
},
error: TypeError: Cannot set property 'Authorization' of undefined
at Function.setHeader (server.js:1556:42)
at Oauth2Scheme._setToken (server.js:1014:31)
at Oauth2Scheme.mounted (server.js:1001:12)
at Auth.mounted (server.js:516:42)
at Auth.init (server.js:459:18)
at module.exports../.nuxt/auth/plugin.js._webpack_exports_.default (server.js:939:16)
at createApp (server.js:2432:87)
}
And my auth module config:
import dotenv from 'dotenv'
dotenv.config()
export const auth = {
plugins: [
// {
// src: '~/plugins/axios',
// ssr: true
// },
{
src: '~/plugins/auth-axios',
ssr: true
},
'~/plugins/auth.js'
],
redirect: {
login: '/',
callback: '/callback'
},
strategies: {
local: false,
facebook: {
client_id: '#############',
userinfo_endpoint:
'https://graph.facebook.com/v2.12/me?fields=about,name,picture{url},email,birthday',
scope: ['public_profile', 'email', 'user_birthday']
},
google: {
client_id:
'#####################'
}
}
}
Seems that auth when login is trying to set the Axios token (also when I log out it tries to remove it) but it fails. If I go to the Chrome dev tools and debug it to see which Axios instance is trying to use for that. Every time is the main Axios instance and it's supposed to be accessible there.
Screenshot from DevTools:
Screenshot from DevTools
Does someone know what I'm missing? Can I prevent auth facebook strategy to update any Axios instance? Or, can I specify which Axios instance to update (set / remove token)?
EDIT: Forcing the $auth.fetchUser my auth plugin code does work but with the error mentioned before. When I try to logOut it doesn't work due to the same error when trying to remove the token automatically)
I'm going crazy with this issue for two weeks now.
Thanks so much!
I tried these code to post data using Axios Api in Vue in I am getting these error:
Access to XMLHttpRequest at
'https://jsonplaceholder.typicode.com/todos' from origin 'null' has
been blocked by CORS policy: No 'Access-Control-Allow-Origin' header
is present on the requested resource. 16_Axios_API_CRUD.html:96 Error:
Network Error
at e.exports (spread.js:25)
at XMLHttpRequest.l.onerror (spread.js:25)
new Vue({
el: "#app",
data() {
return {
todos: [],
newTodo: "",
loading: true,
errored: false
};
},
methods: {
addToDo() {
debugger;
const _todo = {
title: this.newTodo,
completed: false
};
//const { title } = this.newTodo;
axios
.post("https://jsonplaceholder.typicode.com/todos", _todo)
.then(res => (this.todos = [...this.todos, res.data]))
}
},
mounted() {
axios
.get("https://jsonplaceholder.typicode.com/todos?_limit=5")
.then(response => (this.todos = response.data))
}
});
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<div id="app">
<input
v-model.trim="newTodo"
/>
<input
value="Add"
type="button"
v-on:click="addToDo"
/>
<section v-else>
<div v-bind:key="todo.id" v-for="todo in todos">
<div class="todo-item" v-bind:class="{'is-complete':todo.completed}">
<p>
{{todo.title}}
</p>
</div>
</div>
</section>
</div>
Anyone face this error message before? Do I need destructor which I notice in some tutorial before posting it? Not quite sure why some tutorial have destructor though.
Edit: It looks like 'slow processing' issue. I can the posted data after a long time. How do I add some 'animation' to show that it is actually in progress posting the data and in progress of returning it back?
You are using absolute URL's. There are two ways you can handle this.
Configure your server to handle CORS Headers
Configure a local reverse-proxy using a tool like webpack-dev-server or nginx.
If you choose the second, which is recommended over the other, your code will be like this:
axios
.post("/api/todos", _todo)
.then(res => (this.todos = [...this.todos, res.data]))
And in your reverse-proxy, made possible by webpack-dev-server:
module.exports = {
//...
devServer: {
proxy: {
'/api': 'https://jsonplaceholder.typicode.com'
},
secure: true
}
};
More on this:
https://www.freecodecamp.org/news/never-use-an-absolute-path-for-your-apis-again-9ee9199563be/
https://webpack.js.org/configuration/dev-server/#devserverproxy
I'm setting up a website page, and want to submit a form with multiple fields, and the Back-end API handles the request is prepared.
Simplified Code shows below:
<form id="contact-form" #submit="postForm" ref="aGeniusForm">
<input type="text" name="companyName">
<input type="email" name="email">
<textarea type="text" name="question"></textarea>
<input type="file" name="file">
<form>
The Back-End is powered by Sprint Boot, and the Controller code is:
#RestController
#RequestMapping("/v1/emails")
class EmailController(
val emailService: EmailService,
val objectMapper: ObjectMapper
){
#PostMapping("/applications", consumes = ["multipart/form-data"])
#ApiOperation("Send a apply email")
fun sendApplyEmail(
#RequestPart body: ApplyEmailPostDto,
#RequestPart("files", required = false) files: List<MultipartFile>
): EmptyResponse {
emailService.sendApplyEmail(body, files)
return EmptyResponse
}
}
The front-end code that creates an http request, the trouble is in postForm method.
// Form component data
export default {
name: "SignupForm",
components: {},
data() {
return {
return {
form: {
companyName: "",
email: "",
question: ""
},
files: []
};
},
methods: {
postForm() {
let formData = new FormData();
// Blob is the point ↓ ↓ ↓
formData.append(
"body",
new Blob([JSON.stringify(this.form)], {
type: "application/json"
})
))
// It is the point ↑ ↑ ↑
// post request, "multipart/form-data"
axios.post(
URL,
formData, // the data to post
{
headers: {
"Content-type": "multipart/form-data"
}
}
)
.then(response => {
//...
})
.catch(error => {
//...
});
}
}
I know that Blob is not widely supported in mobile browsers and in webviews and I don't think using Blob is necessary.
But back-end developer insisted on making me do so.
If front-end code as below
formData.append("body",this.form))
//or
formData.append("body",JSON.stringify(this.form)))
The Back-end API return code 400 with message: "media-type not supported"
The kotlin developer who designed the API declared that he cannot support other media-type easily.
I'm not quite familiar with Spring Boot framework or kotlin. So I need you guys to tell me is that true?
What's the best practice for this condition?