N
Nuxtā€¢8mo ago
Optio1

Issue reliably getting cookies in API Routes.

How can I reliably get a cookie in the <script> part of one of my .vue files? It works if I click into the URL bar and hit enter, but not when I refresh or get redirected from an external webpage, or using a link inside of my app.
86 Replies
Sandvich
Sandvichā€¢8mo ago
That is way too little information for anyone to help you. You haven't mentioned any packages, functions or shown any code
owljackob
owljackobā€¢8mo ago
Exactly.
Optio1
Optio1OPā€¢8mo ago
I am trying to use jsonwebtokens package to put my own token in a cookie. I realized the way I was doing it was not the best but it did work when I refreshed. Here is the code I used to have:
<script setup lang="ts">
const stateCookie = useCookie('state')
console.log(stateCookie.value)
const jwt = require('jsonwebtoken');
const route = useRoute()
if (stateCookie.value){
const secret = "somevalue"
var decoded = jwt.verify(stateCookie.value, secret)
if (decoded.state === route.query.state) {
console.log('state is correct')
} else {
console.log('state is incorrect')
}
}
</script>
<script setup lang="ts">
const stateCookie = useCookie('state')
console.log(stateCookie.value)
const jwt = require('jsonwebtoken');
const route = useRoute()
if (stateCookie.value){
const secret = "somevalue"
var decoded = jwt.verify(stateCookie.value, secret)
if (decoded.state === route.query.state) {
console.log('state is correct')
} else {
console.log('state is incorrect')
}
}
</script>
I since moved to using middleware, however, im still stuck on how to get the cookie in the middleware. This is what I have for middleware code right now.
export default () => {
console.log('middleware')
const stateCookie = useCookie('state')
console.log(stateCookie.value)
const jwt = require('jsonwebtoken');
const route = useRoute()
if (stateCookie.value){
const secret = "somevalue"
var decoded = jwt.verify(stateCookie.value, secret)
if (decoded.state === route.query.state) {
console.log('state is correct token is ' + route.query.code)
} else {
console.log('state is incorrect')
}
}
}
export default () => {
console.log('middleware')
const stateCookie = useCookie('state')
console.log(stateCookie.value)
const jwt = require('jsonwebtoken');
const route = useRoute()
if (stateCookie.value){
const secret = "somevalue"
var decoded = jwt.verify(stateCookie.value, secret)
if (decoded.state === route.query.state) {
console.log('state is correct token is ' + route.query.code)
} else {
console.log('state is incorrect')
}
}
}
I did also try
export default (event: any) => {
console.log('middleware')
const stateCookie = getCookie(event, 'state')
console.log(stateCookie)
const jwt = require('jsonwebtoken');
const route = useRoute()
if (stateCookie){
const secret = "somevalue"
var decoded = jwt.verify(stateCookie, secret)
if (decoded.state === route.query.state) {
console.log('state is correct token is ' + route.query.code)
} else {
console.log('state is incorrect')
}
}
}
export default (event: any) => {
console.log('middleware')
const stateCookie = getCookie(event, 'state')
console.log(stateCookie)
const jwt = require('jsonwebtoken');
const route = useRoute()
if (stateCookie){
const secret = "somevalue"
var decoded = jwt.verify(stateCookie, secret)
if (decoded.state === route.query.state) {
console.log('state is correct token is ' + route.query.code)
} else {
console.log('state is incorrect')
}
}
}
but getCookie is undefined and using
import { defineEventHandler, setCookie, getCookie } from "h3";
export default defineEventHandler(event => {
let stateCookie = getCookie(event, 'state')
console.log(stateCookie)
})
import { defineEventHandler, setCookie, getCookie } from "h3";
export default defineEventHandler(event => {
let stateCookie = getCookie(event, 'state')
console.log(stateCookie)
})
gives me Cannot read properties of undefined (reading 'req')
Sandvich
Sandvichā€¢8mo ago
export default defineEventHandler(event => {
let stateCookie = getCookie(event, 'state')
console.log(stateCookie)
})
export default defineEventHandler(event => {
let stateCookie = getCookie(event, 'state')
console.log(stateCookie)
})
So this code looks absolutely fine and the 'req' part tells me it might be an error in how you're fetching this endpoint
export default (event: any) => {
console.log('middleware')
const stateCookie = getCookie(event, 'state')
...
export default (event: any) => {
console.log('middleware')
const stateCookie = getCookie(event, 'state')
...
I have no idea what this could be doing as nowhere in the documentation do you set up request handlers or middleware like this, that's just a function https://nuxt.com/docs/guide/directory-structure/middleware And what's wrong with the first block of code? That should work as long as you set the cookie correctly. And why are you comparing it to the query param?
Nuxt
middleware/ Ā· Nuxt Directory Structure
Nuxt provides middleware to run code before navigating to a particular route.
Optio1
Optio1OPā€¢8mo ago
Comparing it to query param to check the state for oauth CSRF validation. The first block of code works perfectly fine ONLY when I click into the url bar and hit enter. Otherwise it gives me this error: The error is because the cookie is undefined, unless I click into the url bar and hit enter.
No description
Sandvich
Sandvichā€¢8mo ago
I don't understand how that's even possible to get this error because you're doing
if (stateCookie.value){
const secret = "somevalue"
var decoded = jwt.verify(stateCookie.value, secret)
if (stateCookie.value){
const secret = "somevalue"
var decoded = jwt.verify(stateCookie.value, secret)
And somehow hitting this codeblock in jwt.verify
No description
Sandvich
Sandvichā€¢8mo ago
are you certain that's the code you're using and getting this error?
Optio1
Optio1OPā€¢8mo ago
I guess I should clarify, if I remove the if statement I get that error, otherwise the cookie is just undefined and my logic fails anyways.
Optio1
Optio1OPā€¢8mo ago
the exact code I used to get that error
No description
Optio1
Optio1OPā€¢8mo ago
Sandvich
Sandvichā€¢8mo ago
oh this is peculiar
Optio1
Optio1OPā€¢8mo ago
oh also hitting refresh does not work
Optio1
Optio1OPā€¢8mo ago
Sandvich
Sandvichā€¢8mo ago
hmm all this weird jankiness could be pointing to some odd browser quirk or the dev environment. Another thought I have is that the cookie hasn't even been set by the time the component renders. Are you correctly setting the cookie in your api oauth callback and only then redirecting to the app?
Optio1
Optio1OPā€¢8mo ago
Yes, cookie is set in the login page before the redirect to discord for oauth.
<script setup lang="ts">

var jwt = require('jsonwebtoken')
const secret = ""
function stringGen() {
var text = ""
let len = 20
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

for (var i = 0; i < len; i++)
text += charset.charAt(Math.floor(Math.random() * charset.length))

return text
}
var state = stringGen()
var token = jwt.sign({ state: state }, secret);

const cookie = useCookie('state', {
maxAge: 15*60,
secure: true,
sameSite: 'strict',
httpOnly: true
})
cookie.value = token

await navigateTo(`https://discord.com/oauth2/authorize?client_id=773293692672016506&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&scope=identify+guilds+email&state=${state}`, {
external: true
})
</script>
<script setup lang="ts">

var jwt = require('jsonwebtoken')
const secret = ""
function stringGen() {
var text = ""
let len = 20
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

for (var i = 0; i < len; i++)
text += charset.charAt(Math.floor(Math.random() * charset.length))

return text
}
var state = stringGen()
var token = jwt.sign({ state: state }, secret);

const cookie = useCookie('state', {
maxAge: 15*60,
secure: true,
sameSite: 'strict',
httpOnly: true
})
cookie.value = token

await navigateTo(`https://discord.com/oauth2/authorize?client_id=773293692672016506&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&scope=identify+guilds+email&state=${state}`, {
external: true
})
</script>
this does successfully set the cookie, I can see it even on the error page. to be fair, I did have the same issue using the normal redirect for the login page. but putting external in the link fixed it.
Optio1
Optio1OPā€¢8mo ago
No description
Optio1
Optio1OPā€¢8mo ago
No description
Sandvich
Sandvichā€¢8mo ago
Can you set cookies like that? Don't u need setCookie?
Optio1
Optio1OPā€¢8mo ago
only in middleware, the cookie setting works flawlessly. documentation gives this example:
No description
Optio1
Optio1OPā€¢8mo ago
in middleware this is the example, however I find it very lacking as the code provided does not work in the slightest for me, and there are no other examples.
No description
Optio1
Optio1OPā€¢8mo ago
wait thats an api route sorry give me a second, im going to try and convert this to an api route instead of a page. LOL API routes work all I did was
export default defineEventHandler(event => {
let stateCookie = getCookie(event, 'state')
console.log(stateCookie)
return {
cookie: stateCookie
}
})
export default defineEventHandler(event => {
let stateCookie = getCookie(event, 'state')
console.log(stateCookie)
return {
cookie: stateCookie
}
})
inside of server/api/callback.ts I should convert login to an api route as well. Thanks for troubleshooting with me though @Sandvich!
Sandvich
Sandvichā€¢8mo ago
Glad you figured it out
Optio1
Optio1OPā€¢8mo ago
šŸ¤¦ā€ā™‚ļø Im at a loss. The cookie is undefined now, untill I hit enter in the url bar again. Also login does not work with internal links, only external ones work. I confirmed this issue is not browser specific, I tried it with edge, edge canary, and firefox. I am still hitting this issue, would this be better addressed on github? I am able to reproduce this issue with the default nuxt app only adding 2 files and the API folder.
Optio1
Optio1OPā€¢8mo ago
only modified things is the API folder and callback.ts and login.ts
No description
Optio1
Optio1OPā€¢8mo ago
callback.ts
import jwt from 'jsonwebtoken';
export default defineEventHandler(event => {
console.log("Callback")
let stateCookie = getCookie(event, 'state')
const secret = "hEl2MLIWr3JU/J5kAnZ8thhkEmZg9DLbBdBVtkpjClI="
console.log("Got Cookie " + stateCookie)
if (stateCookie === undefined) {
console.log("State Cookie is undefined")
// return sendRedirect(event, "/api/login", 307)
}
console.log("State Cookie Checked")
var decoded = jwt.verify(stateCookie, secret)
deleteCookie(event, 'state')
console.log("Deleted Cookie")
if (decoded.state === getQuery(event).state) {
return {
status: 200,
body: "State Validation Passed, You're Safe!"
}
} else {
return {
status: 401,
body: "State Validation Failed, You're Not Safe!"
}
}
})

import jwt from 'jsonwebtoken';
export default defineEventHandler(event => {
console.log("Callback")
let stateCookie = getCookie(event, 'state')
const secret = "hEl2MLIWr3JU/J5kAnZ8thhkEmZg9DLbBdBVtkpjClI="
console.log("Got Cookie " + stateCookie)
if (stateCookie === undefined) {
console.log("State Cookie is undefined")
// return sendRedirect(event, "/api/login", 307)
}
console.log("State Cookie Checked")
var decoded = jwt.verify(stateCookie, secret)
deleteCookie(event, 'state')
console.log("Deleted Cookie")
if (decoded.state === getQuery(event).state) {
return {
status: 200,
body: "State Validation Passed, You're Safe!"
}
} else {
return {
status: 401,
body: "State Validation Failed, You're Not Safe!"
}
}
})

login.ts
import jwt from 'jsonwebtoken';
export default defineEventHandler(event => {
const secret = "hEl2MLIWr3JU/J5kAnZ8thhkEmZg9DLbBdBVtkpjClI="
function stringGen() {
var text = ""
let len = 20
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

for (var i = 0; i < len; i++)
text += charset.charAt(Math.floor(Math.random() * charset.length))

return text
}
var state = stringGen()
var token = jwt.sign({ state: state }, secret);
setCookie(event, 'state', token, {
maxAge: 15*60,
secure: true,
sameSite: 'strict',
httpOnly: true
})
return sendRedirect(event, `https://discord.com/oauth2/authorize?client_id=773293692672016506&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fapi%2Fcallback&scope=identify+guilds+email&state=${state}`, 307)
})
import jwt from 'jsonwebtoken';
export default defineEventHandler(event => {
const secret = "hEl2MLIWr3JU/J5kAnZ8thhkEmZg9DLbBdBVtkpjClI="
function stringGen() {
var text = ""
let len = 20
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

for (var i = 0; i < len; i++)
text += charset.charAt(Math.floor(Math.random() * charset.length))

return text
}
var state = stringGen()
var token = jwt.sign({ state: state }, secret);
setCookie(event, 'state', token, {
maxAge: 15*60,
secure: true,
sameSite: 'strict',
httpOnly: true
})
return sendRedirect(event, `https://discord.com/oauth2/authorize?client_id=773293692672016506&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fapi%2Fcallback&scope=identify+guilds+email&state=${state}`, 307)
})
Optio1
Optio1OPā€¢8mo ago
github repo with the minimal reporoduction https://github.com/Optio1/nuxtbugreproduction
GitHub
GitHub - Optio1/nuxtbugreproduction
Contribute to Optio1/nuxtbugreproduction development by creating an account on GitHub.
Optio1
Optio1OPā€¢8mo ago
Also confirmed this still has problems after build. But the clicking into url bar and hitting enter works in the build as well.
Flo
Floā€¢8mo ago
Just a thing to remember: If your cookie is httpOnly, you can't access it in js.
Optio1
Optio1OPā€¢8mo ago
Even removing httpOnly I still have the same symptoms. HTTP only shouldnt matter in this case, because I need to get the cookie server side. I did also try adding if (process.client) return; and wrapping the entire code in if (process.server) {}.
Flo
Floā€¢8mo ago
You're using useFetch or $fetch to get that endpoint?
Optio1
Optio1OPā€¢8mo ago
neither, redirecting directly to it.
Flo
Floā€¢8mo ago
(Sorry, I did not read the full story)
Optio1
Optio1OPā€¢8mo ago
thats alright, its a long one. Here is a video of the issue. I tried with pages, and api routes.
Optio1
Optio1OPā€¢8mo ago
Il record another using the api routes
Flo
Floā€¢8mo ago
Wait a sec for the recording, please open the chrome debug tools, to the network tab, filter for documents, preserve logs and show the request chain. Preferrably with some seconds between the requests so I can get some details through the compression. The interesting things are the request and response headers of each request.
Optio1
Optio1OPā€¢8mo ago
Optio1
Optio1OPā€¢8mo ago
will do I can also just do this hold on
Flo
Floā€¢8mo ago
(replies might take a while, my pizza is about to ring the doorbell) Oh, and the obvious question: Why not using nuxt-auth or similar? There is support for discord sso
Optio1
Optio1OPā€¢8mo ago
I didint like the lack of documentation on how to use it, and its black boxy, ive built my own oauth halding before and just prefer to use that.
Flo
Floā€¢8mo ago
that's okay šŸ™‚ yeah, docs could be improved. It's basically just a wrapper for authjs, which actually works pretty well
Flo
Floā€¢8mo ago
ouh, that's nice!
Optio1
Optio1OPā€¢8mo ago
am realizing thats mostly discord....
Flo
Floā€¢8mo ago
that's only discord šŸ˜„
Optio1
Optio1OPā€¢8mo ago
yea its not capturing localhost il grab a recording of the network tab
Flo
Floā€¢8mo ago
and more interesting: It has a request to that error reporting proxy. Might not be important though.
Optio1
Optio1OPā€¢8mo ago
Optio1
Optio1OPā€¢8mo ago
no delay in requests but I didnt see exactly where to do that
Flo
Floā€¢8mo ago
doesn't the inspector show any details? like request headers?
Optio1
Optio1OPā€¢8mo ago
yes
Flo
Floā€¢8mo ago
I'd really recommend using chromium/similar for that kind of debugging.
Optio1
Optio1OPā€¢8mo ago
ive got edge if that has it or I can install chrome
Flo
Floā€¢8mo ago
edge is fine
Optio1
Optio1OPā€¢8mo ago
full disclosure im not very familar with chrome dev stuff
Flo
Floā€¢8mo ago
doesn't matter šŸ˜„ I can setup an own test once I devoured those pizza slices
Optio1
Optio1OPā€¢8mo ago
Does a .har file help you at all? I ran the site in edge with persist logs and downloaded them to an har file.
Flo
Floā€¢8mo ago
It should, yeah. Be aware, it may contain secrets, like cookies. Which is what we're interested in. Better share via dm.
Optio1
Optio1OPā€¢8mo ago
not too woried, the cookie has a randomly generated state for my app and thats it.
Flo
Floā€¢8mo ago
alright, send it over
Optio1
Optio1OPā€¢8mo ago
Flo
Floā€¢8mo ago
oh boy. I need some minutes to jump through that
Optio1
Optio1OPā€¢8mo ago
Just because im paranoied... I changed my discord password because thats the only other thing that I was authenticated with in edge.
Flo
Floā€¢8mo ago
šŸ˜„
Optio1
Optio1OPā€¢8mo ago
It cant hurt you know šŸ˜‚
Flo
Floā€¢8mo ago
True. Because I can see your discord jwt šŸ˜„
Optio1
Optio1OPā€¢8mo ago
Figured that may be the case with the whole, routing to discord and authenticating for oauth thing.
Flo
Floā€¢8mo ago
Alright. I see the jwt, I also see an OPTIONS request to your callback. I also see that options request as get request. Both return 500 At the end, I see your manual get request, with the same parameters. This one has a state-cookie.
Optio1
Optio1OPā€¢8mo ago
Ah so do I just need to enable cors is that the whole problem
Flo
Floā€¢8mo ago
that could be oh, your login page is actually setting that state cookie
Optio1
Optio1OPā€¢8mo ago
yes not cors, or atleast not that I would expect, I added the nuxt-security module which should by default add cors to everything.
Flo
Floā€¢8mo ago
I'm somehow missing the redirect from discord back to localhost
Optio1
Optio1OPā€¢8mo ago
šŸ¤·ā€ā™‚ļø maybe I should also note this happens when setting the cookie as well if I click a button in my nuxt app that redirects to the login page that is not expicitly tagged as external, it has the same error, but if I set it to external it works fine.
Optio1
Optio1OPā€¢8mo ago
No description
Flo
Floā€¢8mo ago
well, you'll have the same issue with useFetch and $fetch. They don't send the cookies. I think that's why hard refreshing the page works
Optio1
Optio1OPā€¢8mo ago
with fetch and useFetch you can do with credentials = true and then it sends the cookies
Flo
Floā€¢8mo ago
what? The docs show a more complex thing for that šŸ˜„
Optio1
Optio1OPā€¢8mo ago
really?
Flo
Floā€¢8mo ago
Nuxt
Use Custom Fetch Composable Ā· Nuxt Examples
This example shows a convenient wrapper for the useFetch composable from nuxt. It allows you to customize the fetch request with default values and user authentication token.
Flo
Floā€¢8mo ago
I built myself an easier one:
export async function useBetterFetch<T>(
url: string,
options: Parameters<typeof $fetch>[1] = {}
): Promise<T> {
const requestHeaders = useRequestHeaders(["cookie"]);
const headers = { ...options.headers, ...requestHeaders };
return $fetch<T>(url, { ...options, headers });
}
export async function useBetterFetch<T>(
url: string,
options: Parameters<typeof $fetch>[1] = {}
): Promise<T> {
const requestHeaders = useRequestHeaders(["cookie"]);
const headers = { ...options.headers, ...requestHeaders };
return $fetch<T>(url, { ...options, headers });
}
had this issue with api calls and missing jwt
Optio1
Optio1OPā€¢8mo ago
I tried this using the vue page script components as well, same problem. I mean, I can do something like that custom fetch with the plugins that just returns the token if thats going to work. It just seems unnessesarily complex for something that seems like the current code should just work for.
Flo
Floā€¢8mo ago
I guess that's why nuxt-auth uses a session-endpoint to get its own data šŸ˜„
Optio1
Optio1OPā€¢8mo ago
yea, il try the plugin way if that yeilds the same results IDK what to do
Flo
Floā€¢8mo ago
we're talking about client redirect, right? ah, my brain... I wanted to ask if you tried attaching a debugger to see exactly what's happening...
Optio1
Optio1OPā€¢8mo ago
whats weirder is the login api route when navigating to it renders a 404 error untill I click into it and hit enter, unless its an external link. I can try attaching a debugger tomorrow here. Ok so it works if I set the link in the api route to redirect directly to the app and skip discord which for obvious reasons is not a solution but it is progress šŸ¤¦ā€ā™‚ļø šŸ¤¦ā€ā™‚ļø sameSite: lax fixes the cooke not appearing on discords redirect still does not account for why I need an external link to get to /api/login or it returns 404 but thats alteast work arroundable
Optio1
Optio1OPā€¢8mo ago
I found https://github.com/nuxt/nuxt/issues/20552 šŸ¤¦ā€ā™‚ļø,
GitHub
Session data not available on external redirect to nuxt Ā· Issue #20...
Environment Operating System: Linux Node Version: v18.12.1 Nuxt Version: 3.4.2 Nitro Version: 2.3.3 Reproduction Unfortunately I'm not able to provide a reproduction, since it relies on externa...
Want results from more Discord servers?
Add your server