#Add Cookie to Request Header
1 messages ยท Page 1 of 1 (latest)
I found the following in the documentation:
---
Astro.response.headers.set('Set-Cookie', 'a=b; Path=/;');
---
So I login to Directus with an email and password and it automatically sets an HttpOnly cookie in the browser that refreshes automatically. I just don't know how to attach this to my requests in Astro as the value of directus_refresh_token is dynamic.
What is the end goal you wish to achieve? The directus cookie is likely set on the directus domain for a valid reason. Why are you trying to set the cookie on the domain that hosts the Astro site?
If the goal is to call directus apis from the Astro server side call, cookies are not the right approach.
try using a library like cookie to generate the string then set in headers.set()
I have an SSR site and I would like to add the ability for a user to login. Having logged in I want to create a section where they have a profile page they can edit; effectively a dashboard. Directus sets an HttpOnly cookie on login server side and a JWT token in localstorage on the client side. It is wildly confusing to me how one manages and syncs sessions on the client and server side and I admit that may be because I do not understand what is happening under the hood enough.
@high eagle
First up, this is more of an "how to integrate with Directus" issue than an Astro specific issue. So, if the support mods feel the conversation is off-topic, please let us know where to move it to.
- The interactions with Directus, do you want to do them on the client side, or on the server side. For eg, when the user wants to save their profile information, do you intend to directly call the directus api from the browser, or do you intend to call the directus api from the server (after the user has posted the data to your server)? I normally recommend always using server-to-server calls.
If you need to make calls from the browser only, then the Directus login api needs to be called with mode set to cookie. That way the Directus auth information is securely stored in a http only cookie, which will automatically be sent by the browser when making any Directus api calls from the browser.
If you need to make calls from the server, then the Directus login api needs to be called with the mode set to json. You need to write code to pass this json auth token to your backend server, and store that auth token in a database somewhere.
If you haven't already read it, please read the Directus Authentication documentation
- https://docs.directus.io/reference/introduction.html#authentication
- https://docs.directus.io/reference/authentication.html
Depending on your use case, eg if you already know every user that will ever use your app, and you also control the Directus server, you can pre-generate Static Tokens for every user and directly hardcode that in your server code.
API documentation on authentication in Directus.
An introduction to the REST and GraphQL APIs in Directus.
In either case, the auth token returned from the login api is temporary and you need to add support for detecting expiration and refreshing the token.
Thank you @warm geyser . I would want to do everything on the server. I have read through the Directus auth documentation but still cannot figure this out. Directus is so new that I know it will take time but I'd love to see projects where people are using SSR and sessions for authentication and authorization. I really do appreciate your response.
Sure. Lets talk about server side. You mention users login to the website and see their profile. In my mind I see two authentications here - one to authenticate the user to your website, and one to authenticate the user to Directus. What is the workflow for your users? Do they login to your website using some login mechanism (email-password, social logins etc). Once logged in they "connect" their Directus account (the way one would connect a FB or LinkedIN account to another site).
I would do something like this. I would create a login.js file next to my login.astro file and pass the form data to the login.js file and then do a Directus login in the login.js:
await directus.auth.login({
email: data.email,
password: data.password,
});
This will place either a cookie server side but the problem is I don't know how to set it so that it is in the header of every subsequent request.
Do I create a new response and set the cookie on it? I wish there were some more examples in this regard.
@high eagle
This simplifies things. Since you are calling the Directus api from the server, we definitely don't want to use the cookie mode. We want to use the json mode.
import { Directus } from '@directus/sdk';
const directus = new Directus(url);
With this setup, the directus object on the server is automatically setup to use json mode for the authentication operations. You can check the docs for how to further configure the authentication related options - https://docs.directus.io/reference/sdk.html#constructor . I feel the defaults should be fine for you.
The JS SDK provides an intuitive interface for the Directus API from within a JavaScript-powered project (browsers and Node.js). The default implementation uses Axios for transport and localStorage for storing state.
Now comes the interesting bits. Since you are using the Directus JS SDK, the sdk has its own ways of doing things. Looking at the SDK documentation, we can see that there are two concepts important to us - Authentication Implementation and Storage Implementation. Directus SDK allows us to pass in custom implementations of both.
The Auth implementation is used by the SDK to implement functionality like login, logout, refresh etc. This default implementation is good enough for our use. The auth tokens (when fetched via login/refresh etc) are stored in the Storage implementation. By default, in NodeJS the Directus SDK will use an in-memory storage.
This gives us all the pieces we need. Pseudo code
- Instantiate a new instance of the Directus InMemory storage.
- Read any existing Directus auth token info for the current user from our database.
- If we have any existing valid auth tokens, then save them in the storage.
- Instantiate a new Directus object, passing in our instance of the storage.
- If required, manually refresh the token. If received new tokens, then save them in the db.
- Make any api calls using the Directus object.
This setup will use the default Auth implementation (which already handles refreshing a token). Which will read/write the tokens to our provided memory storage implementation. We manually seed the memory storage with our previously saved auth token.
import {Directus, MemoryStorage} from '@directus/sdk';
const storage = new MemoryStorage();
const directusAuthInfo = readDirectusAuthFromDB(data.userId);
if (directusAuthInfo) {
storage.auth_token = directusAuthInfo.access_token;
storage.auth_refresh_token = directusAuthInfo.refresh_token;
storage.auth_expires = directusAuthInfo.expires;
storage.auth_expires_at = directusAuthInfo.expires_at;
}
const directus = new Directus(url, {storage});
// this might not be needed
try {
const authInfo = await directus.auth.refresh();
saveDirectusAuthToDB(data.userId, authInfo);
} catch(err) {
// inspect the error
console.error(err);
}
// make an api call
const commentId = 100; // some data
const data = await directus.comments.readOne(commentId);
Default in memory storage - https://github.com/directus/sdk/blob/v10.1.4/src/base/storage/memory.ts
Default auth updating the storage - https://github.com/directus/sdk/blob/v10.1.4/src/base/auth.ts#L75
If you don't have access to a database on the server, to save the user tokens to, then we can talk about how to save the tokens to a persistent browser cookie.
@warm geyser This is great. Thank you. I have a JSON token in memory storage and can access the JSON and the token value in the Route API. Directus works well in this regard. My preference would be to have an HttpOnly cookie in the browser so that every time the user makes a request to the Directus API it checks against the HttpOnly cookie.
I think I have a potentially simpler solution. Basically I can set an HttpOnly cookie in the response like so:
return new Response(JSON.stringify(token), {
status: 200,
headers: {
'Set-Cookie': `session_id=${token}; HttpOnly}`,
},
})
This grabs the value of the token that is set server side by Directus. I have created a new session_id field on the Directus User model that I use a post request to set. Basically I have a Cookie with value that I can check against the session_id value now. When you log out you set the session_id back to null and remove the Cookie.
My preference would be to have an HttpOnly cookie in the browser so that every time the user makes a request to the Directus API it checks against the HttpOnly cookie.
I don't understand. The api calls to Directus are made only from your server backend to Directus. As per your description earlier, you don't have any client side js that makes an api call from the browser to Directus.
The new Response code you wrote, where is that running? If your website is hosted at www.example.com, that code will set the cookie with the name session_id on the domain www.example.com. If your browser side client JS code makes an api call to Directus directly from the browser, this cookie will not be sent.
I think you are saying, instead of storing the token and related information (refresh token and expiry) in your own database, you are storing it in Directus itself as a property of the user. Then, you are storing only the token in a http only cookie set on your own domain. Any subsequent request to your server, you get the session_id cookie and auth token, and use that to directly query Directus (possibly using the InMemory storage modification I showed earlier). ... This is interesting ๐
... I am wondering how are you using the auth info stored in the Directus User model?
You can use https://github.com/jshttp/cookie to simplify your cookie code.
Writing
// you may want to set the `secure` attribute only in prod
const cookieStr = cookie.serialize('session_id', token, {httpOnly: true, secure: true});
const response = new Response(JSON.stringify(token), {
status: 200,
headers: {
'Set-Cookie': cookieStr,
},
})
Reading
const token = cookie.parse(Astro.request.headers.get('cookie'))?.session_id;
Glad you got everything worked out ๐
I made a little video which hopefully makes what I am doing a little more clear. I need to keep thinking through this. Your feedback is really helpful. If you'd like to co-code at any point, let me know. Thank you!
Thank you for making the video. It helps. Just to clarify, the client side JS will never directly talk to Directus. It will always talk to your server, which internally will talk to Directus. ie any api call to Directus is always initiated by server side JS? I ask, because towards the end of the video, it sounded like you wish for the client side JS to also support talking to the Directus directly. To keep things simple, can we eliminate the need for client side JS to directly talk to Directus?
Basically there are three places that need to be in harmony:
- Client with HttpOnly cookie
- Server with JSON Token stored in memory - e.g. Nodejs Server
- Database
I'm still at it here and I know have Express JS as my node server and have installed express-session. Just playing with that now and it may be a way forward. I have to believe there are smarter devs than I out there who have done something like this. Any direction would be appreciated.
Hi @high eagle I wish I could help but I have very little exposure to this side of development, if you wish we can arrange for a bit of time together and you can show me this and Ill see what can be done from there
@high eagle what bits are you stuck on?
Overall idea
Your website needs a way to authenticate the current user. Usually a session_id cookie is a pointer to some place where the actual session data is stored. If you want to store the actual data in the session cookie, it will be better security wise to encrypt the data. That way, if someone manages to read the cookie, they don't immediately get actual Directus access token.
-
Simplest approach would be to store the access_token, its refresh_token and the expiry time in the session cookie. Create hashmap with these info, JSON stringify it, encrypt it and then set the cookie, with the cookie expiry time matching the access_token expiry time.
-
Any subsequent request to your server, you will receive the cookie. Decrypt its data. Use the access_token to query current user
await directus.users.me.read();. You will need to configure the directus object, the way I showed in the code in my previous message. ie create a InMemoryStorage, populate it with the access_token and then pass that to the Directus constructor. -
You will need to detect when the token is auto-refreshed by Directus, so that you can save the new access_token, refresh_token and expiry in the session cookie.
This way we use the cookie itself as the "database". No need to store the access_token in the Directus user model.
This is amazing. Iโm not sure I could execute all this as my knowledge in this area is limited. If you would ever like to cocode, let me know. Thank you!
I haven't co-coded with an internet person before. What would it entail? How would we go about it?
Regarding the lack of knowledge, its okay. We all learn by doing and making mistakes and fixing them ๐ . Which parts are you not sure about? If its the encryption bits, we can ignore that for now, and focus on getting the end to end flow working.
the code here is most of the implementation - #1020017443244081263 message ... instead of reading and writing to the db, we read and write to the cookie. ... does that code make sense to you? is it clear to you why we need to manually populate the inmemory storage? (I ask because that was the part I kept tripping over and spent an hour reading the documentation and the code to figure out how to do. So please do ask questions if its not clear why we have to do that).
This is a pretty good example: https://github.com/magnuswahlstrand/astro-supabase-vercel
I was curious about how I might implement the core functionality. I worked off the directus astro example repo and tweaked it to implement login and fetching logged in users data.
Repo - https://github.com/amithgeorge/astro-directus-authentication
Index page - https://github.com/amithgeorge/astro-directus-authentication/blob/12a8cde9688192e3717273c894a02247b95105c8/astro/src/pages/index.astro
This should give you something to work off of. Please ask if you have any questions.
@warm geyser this is really cool,
!!!!!!!! Thank you so much. Iโll have a look first thing in the morning.
i've got cookie issues and a bit pressed for time rn, but have a feeling some of my questions would be addressed in this here thread, so i'll come back here in a few hours. but thanks in advance for the detailed example @warm geyser ๐ !
(not so much an issue as a question really but ๐คท semantics)
Please ask the questions you have, when you have time. Glad to help ๐