I've just recently started using svelte
and sapper
, and I'm trying to persist a stored session even when a user refreshes the page. (I hope this can be done).
The idea is that a user can sign in, and be redirected to the homepage as an authenticated used. BUT when I hit refresh in my browser, the session is empty and user has to go through sign in process over again.
Any Ideas?
So far I could not find a solution. Below some of the files the are involved in this process.
server.js
import sirv from "sirv";
import polka from "polka";
import compression from "compression";
import * as sapper from "@sapper/server";
import bodyParser from "body-parser";
import session from "express-session";
import sessionFileStore from "session-file-store";
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === "development";
const FileStore = sessionFileStore(session);
polka()
.use(
bodyParser.json(),
session({
secret: "secret",
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 31536000,
},
store: new FileStore({
path: process.env.NOW ? `/tmp/sessions` : `.sessions`,
}),
})
)
.use(
compression({ threshold: 0 }),
sirv("static", { dev }),
sapper.middleware({
session: (req) => ({
user: req.session && req.session.user,
}),
})
)
.listen(PORT, (err) => {
if (err) console.log("error", err);
});
login.svelte
<script context="module">
export async function preload({ params }, { user }) {
if (user) {
this.redirect(302, `/`);
}
}
</script>
<script>
import { goto, stores } from "@sapper/app";
import api from "../api.js";
import Button from "../components/Button.svelte";
import Input from "../components/Input.svelte";
import InputPassword from "../components/InputPassword.svelte";
let errors;
let email;
let password;
let disabled;
const { session } = stores();
const handleSubmit = async () => {
try {
errors = null;
disabled = true;
await api.get("/csrf-cookie");
const authToken = await api.post("/login", { email, password });
api.defaults.headers.common["Authorization"] = `Bearer ${authToken.data}`;
const user = await api.get("/me");
session.set({ user: user.data });
disabled = false;
goto("/");
} catch (e) {
errors = e;
disabled = false;
}
};
</script>
<style>
.login-form {
max-width: 35em;
margin: 5% auto;
padding: 2em;
background: rgba(233, 233, 233, 0.5);
border: 1px solid rgba(151, 151, 151, 0.5);
border-radius: 5px;
}
.form-title {
margin-top: 0;
font-size: 1.2em;
}
.error-block {
color: red;
}
</style>
<svelte:head>
<title>Login</title>
</svelte:head>
<div class="login-form">
<h3 class="form-title">Login</h3>
<form on:submit|preventDefault={handleSubmit}>
<Input placeholder="Username" id="email" bind:value={email} />
<InputPassword placeholder="Password" id="password" bind:value={password} />
<Button {disabled} type="submit">Login</Button>
{#if errors}<span class="error-block">{errors}</span>{/if}
</form>
</div>
index.svelte
<script context="module">
export async function preload({ params }, { user }) {
console.log(user); // undefined
if (!user) {
this.redirect(302, `/login`);
}
}
</script>
<h1>Dashboard</h1>
I'm using Laravel 8 sanctum
for Auth.
Not sure what else I need to provide to get to the bottom of this issue.
It appears as though you lifted most of your code from the sapper realworld project (correct me if I'm wrong), but you forgot to implement a server-side 'api route' to add the freshly logged in user to the session.
In the realworld project, when the user logs in, a POST request is made to the server-side /auth/login
route, which is served by the following function:
import * as api from 'api.js';
export function post(req, res) {
const user = req.body;
api.post('users/login', { user }).then(response => {
if (response.user) req.session.user = response.user;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(response));
});
}
What this function does is:
/users/login
endpoint of the realworld project API
user
object, it stores that object into the server-side sessionConsidering you're obviously NOT using the realworld project API to authenticate against, but your own auth process, what you have to add is a similar server-side route as the one above, but one that will:
Considering the API calls you're using to set the user on the client side in your code, that function would look something like this (saving that file as /routes/auth/login.js
for example):
import * as api from 'api.js';
export async function post(req, res) {
const { email, password } = req.body;
const authToken = await api.post("/login", { email, password });
api.defaults.headers.common["Authorization"] = `Bearer ${authToken.data}`;
const user = await api.get("/me");
if (user) req.session.user = user.data;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(user));
}
and the handleSubmit
method in your login.svelte
file becomes:
const handleSubmit = async () => {
try {
errors = null;
disabled = true;
// substitute your auth API request chain with a proxy request to
// the server-side API where you will set the server-side session user
const user = await fetch('/auth/login', {
method: 'POST',
credentials: 'include',
body: JSON.stringify({ email, password }),
headers: { 'Content-Type': 'application/json' },
})
session.set({ user: user.data });
disabled = false;
goto("/");
} catch (e) {
errors = e;
disabled = false;
}
};
Note that in your particular case, you'd probably want to store the auth token in the session as well, to avoid having to request a new token every time you want to make an authenticated request to your data API.
Use the svelte localStorage:
Create a store e.g. myStore.js
import { writable } from 'svelte/store';
export let mystore = writable({
session: ""
});
export function setSession(session) {
mystore.set({
session: session
});
session = session; // refresh UI
}
Subscribe to it in routes/_layout.svelte
<script>
import {mystore, setSession} from './myStore.js'
let session = setSession("A_SESSION"); // here comes the session
const unsubscribeMyStore = mystore.subscribe(value => {
session = session;
});
</script>
<A_COMPONENT bind:session={$mystore}/> // if the component exports session
Use in A_COMPONENT:
<script>
export let session;
</script>
<div>
{session.session}
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With