[Solved] I can't add a cookie to the result of an /api call & use it in the /web side

I can’t figure out [yet] how to add a cookie to the result of an /api call & use it in the /web side ?

I used the Network panel of Chrome to find my /api endpoint: http://localhost:8910/.netlify/functions/graphql

I chose the GraphQL type in Postman

I sent the body to the endpoint & got back the right results – NO COOKIES COME BACK

I set them thusly in /api


// https://www.npmjs.com/package/jwt-simple
const jwt = require('jwt-simple')

// https://www.npmjs.com/package/js-cookie
import Cookies from 'js-cookie'

[...]


        const { accessKey, id: attendeeId, updated_at: generatedAt } = ensured
        const payload = { accessKey, attendeeId, eventId, generatedAt }
        if (!secret) { throw new Error('JWT Secret Required.') }
        const encodedJWT = jwt.encode(payload, secret);
        Cookies.set('attendeeVaccess', encodedJWT)

[...]

I also can’t read cookies in my /api code…

Me Want COOKIE !!!

(yes, I’m that old : )

Cookies are on headers.

You can access headers from the event in a serverless function.

You can also access that in a custom UserContext function in your GraphQL handler.

Or if you need the headers in your getCurrentUser then you have access to the request as req and the headers are there.

1 Like

Thanks @dthyresson, I used what you pointed me at to create a way to register context handlers into the GraphQLHandler – it needs strengthening, and converting to typescript.

But I can’t figure out how to access the response yet…

Would you be able to point me at something of a response nature?

Thanks, as always
Al;

add a custom context handler

.../api/lib/graphql.ts

[...]
import { preRequestContextHandlers } from 'src/lib/context'
[...]
export const handler = createGraphQLHandler({
  [...]
  context: preRequestContextHandlers,
  [...]
}

Defined thusly:

.../api/lib/context.js

import { logger } from 'src/lib/logger'
const preRequestHandlers = [];
export const registerPreRequestContextHandler = fn => preRequestHandlers.push(fn)
export const preRequestContextHandlers = async (argues) => {
  for (let ndx = 0; ndx < preRequestHandlers.length; ndx += 1) {
    try {
      await preRequestHandlers[ndx](argues)
    } catch (err) {
      logger.error(`api/lib/context ~ preRequestContextHandlers ~ `, err.message)
    }
  }
}

Which can be used to register a cookie handler:

.../api/lib/cookie.js

import { logger } from 'src/lib/logger'
const jwt = require('jwt-simple')
import Cookies from 'universal-cookie';
import { registerPreRequestContextHandler } from 'src/lib/context'
export const authCookieCookieName = 'authCookie'
const secret = process.env.myJwtSecret;
let requestCookies = null;
let authCookie = {};
export const  getAuthCookie = () => ({ ...authCookie })
export const readAuthCookies = async ({ event, context }) => {
    try {
        requestCookies = new Cookies(event?.headers?.cookie);
        const accessJWT = requestCookies.get(authCookieCookieName)
        if (accessJWT) {
            if (!secret) { throw new Error('JWT Secret Missing') }
            authCookie = jwt.decode(accessJWT, secret)
        }
    } catch (err) {
      logger.error(`graphql.ts ~ readAuthCookies ~ ${err.message}`)
    }
}
export const writeAuthCookies = async ({ accessKey, attendeeId, generatedAt, eventId }) => {
    try {
      const payload = { accessKey, attendeeId, eventId, generatedAt }
      if (!secret) { throw new Error('JWT Secret Missing') }
      const accessJWT = jwt.encode(payload, secret);
      requestCookies.set(authCookieCookieName, accessJWT, { path: '/', secure: true, httpOnly: true, sameSite: true })
    } catch (err) {
      logger.error(`graphql.ts ~ writeAuthCookies ~ ${err.message}`)
    }
}
registerPreRequestContextHandler(readAuthCookies)

Which can be called from a place where I need those cookies:

.../api/services/someService.js

[...]
import { getAuthCookie } from 'src/lib/cookies'
[...]
const authCookie = getAuthCookie()
if (authorized(authCookie)) {
   [then proceed]
}
[...]

Now, to write the cookie into a response…

Have you looked at self-hosted dbAuth here?

It uses cookies and JWTs.

Is that what you are trying to implement?

Maybe some code in redwood/packages/api/src/functions/dbAuth at main · redwoodjs/redwood · GitHub will give you ideas.

As for the response, if you are invoking a serverless function called “cookie” and adding it the the response headers, then the headers are there.

But I don’t see you sending the response.

Could you explain why and what you want to solve vs the how?

Ie, I need to authenticate in a different way in order to “do this” that I can’t do otherwise.

Instead, of I need cookies on the request.

That might help us solve the problem better.

Yes, a quick explaination would very likely help.

Our users will login via Google/Facebook/Social and will pay for access to create Events that can be attended by people who have no login

I’m authenticating Attendee’s via a 2FA on their phones, which generates a secure server-only cookie that will be used as their password and write/read their data

For now I’m returning the JWT via the GraphQL response & saving it into the users’ cookies

Then I’m using my new context handler to extract that auth if it’s there and my code is now working in that fashion

I would like to write that cookie into the response so that it’s not passing thru the client side JS and can be set as server-only

I’ll look into the link you sent, I’m sure it’ll give me what I want

Thanks!

Thanks for the description/flow – really helps.

This is tricky.

Questions/

  1. The Attendee with their JWT cookie, will they then interact with the app? Meaning, they are authenticated and permitted to “do things” with the app?
  2. When I an invited to events with Livestorm or Hopin, I register and become a user. Is that an option?

Ie. Supapabase now has a user/password auth flow. And then also have a signup/in flow with phone and one-time password. But, the provider in each case is still Supabase. And the access token is still the same type. Just the UI and UX for the user and the attendee is different. But they are still user accounts in Supabase.

See: Supabase Auth v2: Phone Auth now available

These signin/up methods will be in the next release 0.36.

Yeah, it’s a bit tricky - I made the cookie a signed JWT with enough info in it to verify the attendee owns the data && still allow the event planner to access it as well

The JWT secret is never given to the web, but that cookie contains one value that I’d like to encrypt when I get the time

Everything is working, my only shame is that I was unable to set the cookie server-side and have to return it as GQL payload for insertion from the webSide

The Supabase option is interesting, my 1st limited go-live is in 10 days :cowboy_hat_face: – maybe I’ll switch afterwards

The Attendee’s interact with the app, yes - but they do not hold User Accounts

User Accounts are for people that want to pay for powers and abilities beyond those of Attendees

Attendee’s interaction is to be super lightweight and yet safe and secure (gotta love the salesmen who invent these rules : ) [Where would we be without them?]

I used code from here: Local JWT Auth Implementation - #11 by viperfx

That did the trick!

easy to start with is his deprecated branch, even tho’ the main branch is where I’ll likely end up