Svelte Sapper сохраняет сеанс при обновлении страницы (перезагрузка)

Я только недавно начал использовать svelte и sapper, и я пытаюсь сохранить сохраненный сеанс, даже когда пользователь обновляет страницу. (Надеюсь, это можно сделать).

Идея состоит в том, что пользователь может войти в систему и быть перенаправлен на домашнюю страницу в качестве аутентифицированного. НО, когда я нажимаю кнопку «Обновить» в своем браузере, сеанс пуст, и пользователь должен снова пройти процесс входа в систему.

Любые идеи?

Пока не нашел решения. Ниже приведены некоторые файлы, участвующие в этом процессе.

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>

Я использую Laravel 8 sanctum для аутентификации.

Не уверен, что еще мне нужно предоставить, чтобы разобраться в этой проблеме.


person Andrejs Gubars    schedule 18.10.2020    source источник


Ответы (2)


Похоже, вы взяли большую часть своего кода из проекта sapper realworld (поправьте меня, если я неправильно), но вы забыли реализовать серверный «api route», чтобы добавить в сеанс только что вошедшего пользователя.

В проекте realworld, когда пользователь входит в систему, выполняется запрос POST на серверный /auth/login маршрут, который обслуживается следующей функцией:

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));
    });
}

Что делает эта функция:

  1. он передает запрос /users/login конечной точке API проекта realworld
  2. если ответ на этот запрос содержит объект user, он сохраняет этот объект в сеансе на стороне сервера
  3. он возвращает исходный ответ API в виде JSON обратно в приложение, где он используется для заполнения хранилища сеансов (если исходный ответ содержал объект пользователя)

Учитывая, что вы, очевидно, НЕ используете API реального мира для аутентификации, а ваш собственный процесс аутентификации, вам нужно добавить такой же серверный маршрут, как и приведенный выше, но который:

  • вместо этого передайте запрос на вход в ваш собственный процесс,
  • введите ответ и используйте этот ответ для установки пользователя сеанса,
  • и, наконец, передать ответ клиенту (или, в качестве альтернативы, передать либо пользовательский объект, сохраненный в сеансе, либо сообщение об ошибке).

Учитывая вызовы API, которые вы используете для установки пользователя на стороне клиента в вашем коде, эта функция будет выглядеть примерно так (например, сохраните этот файл как /routes/auth/login.js):

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));
}

и метод handleSubmit в вашем login.svelte файле станет следующим:

  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;
    }
  };

Обратите внимание, что в вашем конкретном случае вы, вероятно, также захотите сохранить токен аутентификации в сеансе, чтобы избежать необходимости запрашивать новый токен каждый раз, когда вы хотите сделать аутентифицированный запрос к вашему API данных.

person Thomas Hennes    schedule 19.10.2020
comment
Прикольные подсказки по реализации авторизации в сапере. В этом контексте я использовал ваши отличные тренировки. Лучший нологин - person nologin; 17.01.2021

Используйте изящный localStorage:

Создайте магазин, например myStore.js

import { writable } from 'svelte/store';

export let mystore = writable({
    session: ""
});


export function setSession(session) {
    mystore.set({
        session: session
    });
    session = session; // refresh UI
}

Подпишитесь на него в 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

Использовать в A_COMPONENT:

<script>
    export let session;
</script>

<div>
{session.session}
</div>
person nologin    schedule 23.10.2020
comment
Использование localStorage - это в лучшем случае обходной путь. Сервер все еще не знает, что сеанс инициализирован, истечение срока сеанса не обрабатывается и т. Д. LocalStorage действительно является способом сохранения данных при перезагрузках, но это не замена правильного управления сеансом один к одному. - person Thomas Hennes; 24.10.2020
comment
@nologin Мне интересна ваша идея, потому что я пытаюсь сохранить некоторое состояние пользовательского интерфейса на клиенте во время навигации по приложению Sapper. Однако я не работаю и даже не реализую то, что вы говорите (в вашем коде нет ссылки на localStorage). Было бы очень полезно, если бы вы могли отредактировать свой ответ полным примером. - person Nicolas Le Thierry d'Ennequin; 15.01.2021
comment
@ NicolasLeThierryd'Ennequin Произошла ошибка в _layout.svelte. Доступной для записи переменной нужен префикс $ as. Я создал рабочее репо: github.com/ivosdc/sapper-appstore-example - person nologin; 17.01.2021
comment
Я нашел другую реализацию с localStorage: chasingcode.dev/blog/svelte- постоянное хранение данных между состояниями - person nologin; 17.01.2021