簡體   English   中英

HTTPOnly Cookie 未在瀏覽器 localhost 中設置

[英]HTTPOnly Cookie not being set in browser localhost

問題

我有一個具有登錄端點的 REST API。 登錄端點接受用戶名和密碼,服務器通過發送包含一些有效負載(如 JWT)的 HTTPOnly Cookie 進行響應。

我一直使用的方法已經工作了幾年,直到上周Set-Cookie標頭停止工作 由於我正在開發基於 Svelte 的前端,因此我在 REST API 無法使用之前沒有接觸過它的源代碼。

我懷疑它與在本地主機中設置為falseSecure屬性有關。 但是,根據Using HTTP cookies ,只要它是本地主機,就可以使用不安全的連接。 一段時間以來,我一直在以這種方式開發 REST API,並且驚訝地發現不再設置 cookie。

使用 Postman 測試 API 會產生設置 cookie 的預期結果。

使用的方法

我試圖重新創建真實 API 的一般流程並將其剝離到其核心要素。

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
    "github.com/golang-jwt/jwt/v4"
)

const idleTimeout = 5 * time.Second

func main() {
    app := fiber.New(fiber.Config{
        IdleTimeout: idleTimeout,
    })

    app.Use(cors.New(cors.Config{
        AllowOrigins:     "*",
        AllowHeaders:     "Origin, Content-Type, Accept, Range",
        AllowCredentials: true,
        AllowMethods:     "GET,POST,HEAD,DELETE,PUT",
        ExposeHeaders:    "X-Total-Count, Content-Range",
    }))

    app.Get("/", hello)
    app.Post("/login", login)

    go func() {
        if err := app.Listen("0.0.0.0:8080"); err != nil {
            log.Panic(err)
        }
    }()

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)

    _ = <-c
    fmt.Println("\n\nShutting down server...")
    _ = app.Shutdown()
}

func hello(c *fiber.Ctx) error {
    return c.SendString("Hello, World!")
}

func login(c *fiber.Ctx) error {
    type LoginInput struct {
        Email string `json:"email"`
    }

    var input LoginInput

    if err := c.BodyParser(&input); err != nil {
        return c.Status(400).SendString(err.Error())
    }

    stringUrl := fmt.Sprintf("https://jsonplaceholder.typicode.com/users?email=%s", input.Email)

    resp, err := http.Get(stringUrl)
    if err != nil {
        return c.Status(500).SendString(err.Error())
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return c.Status(500).SendString(err.Error())
    }

    if len(body) > 0 {
        fmt.Println(string(body))
    } else {
        return c.Status(400).JSON(fiber.Map{
            "message": "Yeah, we couldn't find that user",
        })
    }

    token := jwt.New(jwt.SigningMethodHS256)
    cookie := new(fiber.Cookie)

    claims := token.Claims.(jwt.MapClaims)
    claims["purpose"] = "Just a test really"

    signedToken, err := token.SignedString([]byte("NiceSecret"))
    if err != nil {
        // Internal Server Error if anything goes wrong in getting the signed token
        fmt.Println(err)
        return c.SendStatus(500)
    }

    cookie.Name = "access"
    cookie.HTTPOnly = true
    cookie.Secure = false
    cookie.Domain = "localhost"
    cookie.SameSite = "Lax"
    cookie.Path = "/"
    cookie.Value = signedToken
    cookie.Expires = time.Now().Add(time.Hour * 24)

    c.Cookie(cookie)

    return c.Status(200).JSON(fiber.Map{
        "message": "You have logged in",
    })
}

這基本上是查看 JSON 占位符的用戶,如果它找到一個匹配的電子郵件,它會發送帶有一些數據的 HTTPOnly Cookie。

看到我正在使用的庫可能有問題,我決定用 Express 編寫一個 Node 版本。

import axios from 'axios'
import express from 'express'
import cookieParser from 'cookie-parser'
import jwt from 'jsonwebtoken'

const app = express()

app.use(express.json())
app.use(cookieParser())
app.use(express.urlencoded({ extended: true }))
app.disable('x-powered-by')

app.get("/", (req, res) => {
    res.send("Hello there!")
})

app.post("/login", async (req, res, next) => {
    try {
        const { email } = req.body

        const { data } = await axios.get(`https://jsonplaceholder.typicode.com/users?email=${email}`)

        if (data) {
            if (data.length > 0) {
                res.locals.user = data[0]
                next()
            } else {
                return res.status(404).json({
                    message: "No results found"
                })
            }
        }
    } catch (error) {
        return console.error(error)
    }
}, async (req, res) => {
    try {
        let { user } = res.locals

        const token = jwt.sign({
            user: user.name
        }, "mega ultra secret sauce 123")

        res
            .cookie(
                'access',
                token,
                {
                    httpOnly: true,
                    secure: false,
                    maxAge: 3600
                }
            )
            .status(200)
            .json({
                message: "You have logged in, check your cookies"
            })
    } catch (error) {
        return console.error(error)
    }
})

app.listen(8000, () => console.log(`Server is up at localhost:8000`))

這兩個都不適用於我測試過的瀏覽器。

結果

Go 對此作出回應。

HTTP/1.1 200 OK
Date: Mon, 21 Feb 2022 05:17:36 GMT
Content-Type: application/json
Content-Length: 32
Vary: Origin
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Total-Count,Content-Range
Set-Cookie: access=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwdXJwb3NlIjoiSnVzdCBhIHRlc3QgcmVhbGx5In0.8YKepcvnMreP1gUoe_S3S7uYngsLFd9Rrd4Jto-6UPI; expires=Tue, 22 Feb 2022 05:17:36 GMT; domain=localhost; path=/; HttpOnly; SameSite=Lax

對於 Node API,這是響應標頭。

HTTP/1.1 200 OK
Set-Cookie: access=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiTGVhbm5lIEdyYWhhbSIsImlhdCI6MTY0NTQyMDM4N30.z1NQcYm5XN-L6Bge_ECsMGFDCgxJi2eNy9sg8GCnhIU; Max-Age=3; Path=/; Expires=Mon, 21 Feb 2022 05:13:11 GMT; HttpOnly
Content-Type: application/json; charset=utf-8
Content-Length: 52
ETag: W/"34-TsGOkRa49turdlOQSt5gB2H3nxw"
Date: Mon, 21 Feb 2022 05:13:07 GMT
Connection: keep-alive
Keep-Alive: timeout=5

客戶來源

我將其用作發送和接收數據的測試表單。

<script>
    let email = "";

    async function handleSubmit() {
        try {
            let response = await fetch(`http://localhost:8000/login`, {
                method: "POST",
                body: JSON.stringify({
                    email,
                }),
                headers: {
                    "Content-Type": "application/json",
                },
            });

            if (response) {
                console.info(response);
                let result = await response.json();

                if (result) {
                    console.info(result);
                }
            }
        } catch (error) {
            alert("Something went wrong. Check your console.");
            return console.error(error);
        }
    }
</script>

<h1>Please Login</h1>

<svelte:head>
    <title>Just a basic login form</title>
</svelte:head>

<form on:submit|preventDefault={handleSubmit}>
    <label for="email">Email:</label>
    <input
        type="email"
        name="email"
        bind:value={email}
        placeholder="enter your email"
    />
</form>

附加信息

郵遞員: 9.8.3

語言版本

去: 1.17.6

Node.js: v16.13.1

苗條 3.44.0

使用的瀏覽器

火狐瀏覽器: 97.0.1

微軟邊緣: 98.0.1108.56

鉻: 99.0.4781.0

解決方案

原來問題出在前端,特別是 JavaScript 的fetch()方法。

let response = await fetch(`http://localhost:8000/login`, {
                method: "POST",
                credentials: "include", //--> send/receive cookies
                body: JSON.stringify({
                    email,
                }),
                headers: {
                    "Content-Type": "application/json",
                },
            });

您需要credentials: includeRequestInit對象中包含屬性,不僅用於發出需要 cookie 身份驗證的請求,還用於接收所述 cookie。

Axios 通常會自動填寫這部分(根據經驗),但如果沒有,您還需要在請求的第三個config參數上加上withCredentials: true以允許瀏覽器設置 cookie。

我剛剛遇到了與axios相同的問題,這導致Set-Cookie響應標頭被靜默忽略。 這很煩人,如果它拒絕它們,它會在該標題上顯示那個小黃色三角形,並在網絡檢查器中說明原因。

我通過添加一個請求攔截器來強制每個請求都為true解決了這個問題:

axios.interceptors.request.use(
    (config) => {
      config.withCredentials = true
      return config
    },
    (error) => {
      return Promise.reject(error)
    }
  )

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM