SvelteKit: cookies.set() In Form Action Not Working - javascript

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.

Related

How do I create a cookie in svelte and access it?

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.

Async custom validator approves the field validation requirement while the promise is still resolving, how can I change the behaviour? Vuelidate

The field is email, and it's the last field the user has to complete in order to send an invitation to an external user to join the platform.
the validation checks if the email is already being used, basically ⇒ if the email is already registered ⇒ throw error. This maintains the submit button disabled.
The issue I'm having here is that until the promise resolves (for this punctual validation), which is about 1/2sec the field turns ok validated and the submit button becomes enable.
If the user is quick (and you know, there's connection speed and other factors in the equation...) he can send an invitation and cause trouble/ terrific user excperience.
how can i make it work backwards => field not approved until promise finished?
Here's the code:
<template>
<Input
id="invite-email"
name="invite-email"
class="w-full"
:errors="v$.email.$errors"
v-model="v$.email.$model"
:invitePlayer="true"
>
{{ t('placeholder.email') }}
</Input>
</template>
<script lang="ts">
import { useVuelidate } from '#vuelidate/core'
import { required, email, minLength, helpers } from '#vuelidate/validators'
import { checkEmail } from '#/utils/validators'
const v$ = useVuelidate(rules, form)
const invite = async (): Promise<void> => {
loading.value = true
try {
const invitedUserResult: Guest = await store.dispatch('inviteUser', {
...Object.fromEntries(
Object.entries(form).filter(([_, v]) => v !== '')
),
fromUserID: store.state.user._id
})
console.log('invitedUserResult', invitedUserResult)
invitedUser.value = invitedUserResult
invitationLink.value = invitedUserResult.shortLink
showInviteSuccess.value = true
} catch (err) {
console.log(err)
}
loading.value = false
}
</script>
I tried to keep only the relevant code, tell me if you need more, thanks a lot!
I think you should use loading value to keep field disabled when promise hasn't been resolved yet. You can pass it as a prop to the component.
I also recommed to wrap loading.value = false with finally to be sure it's always executed, even if there is an error thrown.
try {...}
catch (err) {
console.log(err)
} finally {
loading.value = false
}

Email.js Works Locally, But not Once Deployed With React

I have set up Email.js to make a contact page for a website built with Next.js. It works completely fine when run locally, but does not work when hosted. The form does not even reset when the submit button is clicked. I do this in the sendEmail function. The error handler does not trigger either in the .then block. I get this error in the browser console:
Uncaught The user ID is required. Visit https://dashboard.emailjs.com/admin/integration
Here is how I send the emails:
export default function Book(props) {
const form = useRef();
const [sentMessage, setSentMessage] = useState();
const sendEmail = (e) => {
e.preventDefault();
emailjs
.sendForm(
props.SERVICE_ID,
props.EMAIL_TEMPLATE_ID,
form.current,
props.USER_ID
)
.then(
function (response) {
setSentMessage("Message sent successfully!");
},
function (error) {
setSentMessage("Message failed please email directly.");
}
);
document.getElementById("form").reset();
};
return (
<div className={styles.container}>
<div className={styles.formContainer}>
<form
className={styles.form}
ref={form}
onSubmit={sendEmail}
id="form"
>
<h3>Name (required):</h3>
<input type="text" required={true} name="user_name"></input>
<h3>Email (required):</h3>
<input type="email" required={true} name="user_email"></input>
<h3>Phone number (required):</h3>
<input type="number" required={true} name="phone_number"></input>
<h3>Message (optional):</h3>
<textarea name="message"></textarea>
<button type="submit" value="Send">
Submit
</button>
{sentMessage ? <p>{sentMessage}</p> : <p></p>}
</form>
</div>
</div>
);
}
export async function getServerSideProps() {
return {
props: {
USER_ID: process.env.USER_ID,
EMAIL_TEMPLATE_ID: process.env.EMAIL_TEMPLATE_ID,
SERVICE_ID: process.env.SERVICE_ID,
},
};
}
I have a .env.local file with the template id, user id and service id that all work fine locally. I use next-env and dotenv-load in the next.config.js file like so:
dotenvLoad();
const withNextEnv = nextEnv();
module.exports = withNextEnv({
reactStrictMode: true,
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ["#svgr/webpack"],
});
return config;
},
});
I saw some problems online that people had with Gmail and remote email servers, so I switched the account to have no 2 factor authentication and use less secure apps as well. That had no effect.
All you need to do is set up the environment variables in the next.js dashboard then rebuild the site so they take effect.

localStorage onClick button display undefined values?

I am trying to display in localstorage the values I get from my api by clicking on a button.
But it seems that the value is not displayed, but the api call has been made, my value is undefined, so I have to click a second time on the button for my value to be displayed.
there is my code :
<template>
<div class="container">
<h1>SignIn</h1>
<div class="overlay-container">
<form>
<div class="form-group">
<input
type="text"
class="form-control"
placeholder="First name"
id="FirstName"
/>
</div>
<div class="form-group">
<input
type="text"
class="form-control"
placeholder="Last name"
id="Lastname"
/>
</div>
<div class="form-group">
<input
type="email"
class="form-control"
placeholder="Email"
id="exampleInputEmail1"
aria-describedby="emailHelp"
/>
</div>
<div class="form-group">
<input
type="password"
placeholder="Password"
class="form-control"
id="exampleInputPassword1"
/>
</div>
<button #click="signIn" type="button" class="btn btn-primary">Sign In</button>
</form>
</div>
</div>
</template>
<script>
import axios from 'axios'
import VueAxios from 'vue-axios'
export default {
name: "Signin",
data(){
return {
userName:'',
userid:''
}
},
methods:{
signIn(){
axios.get(`http://localhost:4000/api/users/2`)
.then(Response => this.userid = Response.data.data.id);
axios.get(`http://localhost:4000/api/users/2`)
.then(Response => this.UserName = Response.data.data.fname);
localStorage.userid = this.userid;
localStorage.UserName = this.UserName;
}
}
};
</script>
some screenshot : First CLick | Second Click
Do you have any idea of whats the problem ?
This should work. https://www.w3schools.com/jsref/met_storage_setitem.asp
async signIn() {
await axios.get(`http://localhost:4000/api/users/2`)
.then(Response => this.userid = Response.data.data.id);
await axios.get(`http://localhost:4000/api/users/2`)
.then(Response => this.UserName = Response.data.data.fname);
localStorage.setItem('userid', this.userid);
localStorage.setItem('UserName', this.UserName);
}
The values from your api calls are only available inside the .then function. So what's happening is, you're setting the keys/values on localStorage directly, before the promise returned from axios.get has resolved.
The reason you are only seeing the values reflected in localStorage after the second click has to do with promises and the order of execution. The first time you click the button, you're sending two GET requests, and then immediately updating localStorage with this.userid / this.UserName, both of which are still empty strings. After that function finished executing, the promise from axios.get resolves, and the values of this.userid / this.UserName are updated by the .then function. The second time you click the button, the localStorage values are updated with the values of this.userid / this.UserName which were previously set.
Also, bear in mind, you should use localStorage.setItem('key', 'value') instead of localStorage.key = 'value'.
The bottom line is, you should move the code that stores key/value pairs in localStorage inside of the .then.
You are not waiting for the ajax (xhr) to complete before saving the values in local storage. .then is the function called after the xhr is complete, so your code to save the values in local storage should be inside .then
axios.get(`http://localhost:4000/api/users/2`)
.then(function(Response) {
this.userid = Response.data.data.id
localStorage.setItem('userid', this.userid);
this.UserName = Response.data.data.fname
localStorage.setItem('UserName', this.UserName);
});
Because both your API calls are to the same endpoint, I have also combined them into one.
Now about your button click:
If you need to show data on click then you should only allow click after the data is received from the API call. To do this you can disable the button until .then is complete. And then re-enable it.
//**button is disabled when page loaded**
axios.get(`http://localhost:4000/api/users/2`)
.then(function(Response) {
this.userid = Response.data.data.id
localStorage.setItem('userid', this.userid);
this.UserName = Response.data.data.fname
localStorage.setItem('UserName', this.UserName);
//**enable button here**
});
Now the fact that you are making the call to your API even when data is stored in localstorage:
I am assuming that you are storing the data in localstorage so that you don't have to make another call to fetch it again.
But currently, your code always makes the API call even when the data is aready presnt in local storage.
You can fix it like this:
//**button is disabled when page loaded**
methods:{
signIn(){
var uId = localStorage.getItem('userid');
var uName = localStorage.getItem('UserName');
if(uId && uName) {
//**enable button here**
}
}
getData() {
axios.get(`http://localhost:4000/api/users/2`)
.then(function(Response) {
this.userid = Response.data.data.id
localStorage.setItem('userid', this.userid);
this.UserName = Response.data.data.fname
localStorage.setItem('UserName', this.UserName);
//**enable button here**
});
}
}
Call getData when your page loads up and then enable the button. So, when the button is clicked the data, is available!
1.) Try onclick instead of #click.
2.) Log to the Console the data you received from localhost:4000

Axios still sending cookies to back end even if 'withCredentials: false'

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.

Categories