【问题标题】:Svelte Sapper retain session on page refresh (reload)Svelte Sapper 在页面刷新时保留会话(重新加载)
【发布时间】:2021-01-17 22:14:23
【问题描述】:

我最近才开始使用sveltesapper,即使用户刷新页面,我也试图保持存储的会话。 (我希望可以做到)。

这个想法是用户可以登录,并作为经过身份验证的用户被重定向到主页。 但是当我在浏览器中点击刷新时,会话是空的,用户必须重新进行登录过程。

有什么想法吗?

到目前为止,我找不到解决方案。下面是这个过程中涉及的一些文件。

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 进行身份验证。

不确定我还需要提供什么才能查明此问题的真相。

【问题讨论】:

    标签: javascript svelte sapper


    【解决方案1】:

    您似乎从sapper realworld 项目中提取了大部分代码(如果我错了,请纠正我),但您忘记实现服务器端“api 路由”以将新登录的用户添加到会议。

    在现实世界的项目中,当用户登录时,会向服务器端/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));
        });
    }
    

    这个函数的作用是:

    1. 它将请求中继到realworld project API/users/login 端点
    2. 如果对该请求的响应包含一个user 对象,它将将该对象存储到服务器端会话中
    3. 它将原始 API 响应以 JSON 形式返回给应用,用于填充会话存储(如果原始响应包含用户对象)

    考虑到您显然 NOT 使用真实世界的项目 API 进行身份验证,但是您自己的身份验证过程,您必须添加的是与上述类似的服务器端路由,但是一个这将:

    • 改为将登录请求中继到您自己的进程,
    • 填写响应并使用该响应设置会话用户,
    • 最后将响应传递给客户端(或者,传递存储到会话中的用户对象或错误消息)。

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

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

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

    【讨论】:

    • 在 sapper 中实现授权的酷提示。在这种情况下,我使用了您出色的锻炼方法。最好的nologin
    【解决方案2】:

    使用纤巧的本地存储:

    创建一个商店,例如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>
    

    【讨论】:

    • 使用 localStorage 充其量是一种解决方法。服务器仍然不知道会话已初始化,会话过期未处理等。LocalStorage 确实是一种在重新加载时持久保存数据的方法,但它不是正确会话管理的一对一替代品。
    • @nologin 我对您的想法很感兴趣,因为我试图在导航 Sapper 应用程序时在客户端上保留一些 UI 状态。但是,我没有工作,甚至没有实现你所说的(你的代码中没有提到localStorage)。如果您可以使用完整的示例编辑您的答案,那将非常有帮助。
    • @NicolasLeThierryd'Ennequin _layout.svelte 中有错误。可写变量需要一个 $ 作为前缀。我创建了一个工作仓库:github.com/ivosdc/sapper-appstore-example
    • 我找到了另一个使用 localStorage 的实现:chasingcode.dev/blog/svelte-persist-state-to-localstorage
    猜你喜欢
    • 2016-06-26
    • 2020-10-01
    • 2012-08-01
    • 2022-11-27
    • 2018-11-14
    • 1970-01-01
    • 2021-09-10
    • 2013-01-31
    • 1970-01-01
    相关资源
    最近更新 更多