简体   繁体   English

Svelte Sapper 在页面刷新时保留会话(重新加载)

[英]Svelte Sapper retain session on page refresh (reload)

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.我最近才开始使用sveltesapper ,即使用户刷新页面,我也试图保留存储的会话。 (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.我正在使用Laravel 8 sanctum进行身份验证。

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.看起来好像您从sapper realworld项目中提取了大部分代码(如果我错了,请纠正我),但是您忘记实现服务器端“api 路由”以将新登录的用户添加到会话中。

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:在实际项目中,当用户登录时,会向服务器端/auth/login路由发出 POST 请求,该请求由以下函数提供:

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:这个函数的作用是:

  1. it relays the request to the /users/login endpoint of the realworld project API它将请求中继到现实世界项目 API/users/login端点
  2. if the response to that request contains a user object, it stores that object into the server-side session如果对该请求的响应包含user对象,则将该对象存储到服务器端会话中
  3. it returns the original API response as JSON back to the app, where it is used to populate the session store (if the original response contained a user object)它将原始 API 响应作为 JSON 返回给应用程序,用于填充会话存储(如果原始响应包含用户对象)

Considering 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:考虑到您显然不是使用真实世界的项目 API 进行身份验证,而是使用您自己的身份验证过程,您必须添加的是与上述类似的服务器端路由,但它将:

  • relay the login request to your own process instead,将登录请求中继到您自己的进程,
  • field the response and use that response to set the session user,字段响应并使用该响应来设置会话用户,
  • and finally pass the response on to the client (or, alternatively, pass either the user object stored into the session or an error message otherwise).最后将响应传递给客户端(或者,传递存储到会话中的用户对象或其他错误消息)。

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):考虑到您在代码中用于在客户端设置用户的 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));
}

and the handleSubmit method in your login.svelte file becomes:并且login.svelte文件中的handleSubmit方法变为:

  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.请注意,在您的特定情况下,您可能还希望在会话中存储身份验证令牌,以避免每次向数据 API 发出经过身份验证的请求时都必须请求新令牌。

Use the svelte localStorage:使用 svelte localStorage:

Create a store eg myStore.js创建一个商店,例如 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在 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:在 A_COMPONENT 中使用:

<script>
    export let session;
</script>

<div>
{session.session}
</div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM