W
Wasp-langโ€ข6mo ago
TomD

use auth when requesting a custom api without the api wrapper

Hello ! I'm having a small trouble, I've seen the example here to implement streaming capabilities. https://github.com/wasp-lang/wasp/tree/main/examples/streaming/src/client I tried to do the same thing and it is working. I am now adding authentification and I can't make it work, indeed since I'm using "fetch" and not the API wrapper I don't get the context.user to do auth. The problem, when I try to access "context.user" it doesn't exist and I cannot make it work. Do you have any ideas ? See the code below.
export async function fetchChatResponseStream(
apiUrl: string,
path: string,
onData: (chunk: string) => void,
controller: AbortController,
method: string = 'POST',
requestBody: ChatResponseStreamBody
) {
const response = await fetch(apiUrl + path, {
method: method,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});

const reader = response.body?.pipeThrough(new TextDecoderStream()).getReader();
while (reader) {
const { done, value } = await reader.read();
if (done) {
return;
}
onData(value.toString());
}
}
export async function fetchChatResponseStream(
apiUrl: string,
path: string,
onData: (chunk: string) => void,
controller: AbortController,
method: string = 'POST',
requestBody: ChatResponseStreamBody
) {
const response = await fetch(apiUrl + path, {
method: method,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});

const reader = response.body?.pipeThrough(new TextDecoderStream()).getReader();
while (reader) {
const { done, value } = await reader.read();
if (done) {
return;
}
onData(value.toString());
}
}
export const chatResponse: ChatResponse = async (request, response, context) => {
if (!context.user) {
response.status(401).json({ error: "Unauthorized" });
return;
}
};
export const chatResponse: ChatResponse = async (request, response, context) => {
if (!context.user) {
response.status(401).json({ error: "Unauthorized" });
return;
}
};
GitHub
wasp/examples/streaming/src/client at main ยท wasp-lang/wasp
The fastest way to develop full-stack web apps with React & Node.js. - wasp-lang/wasp
9 Replies
kapa.ai
kapa.aiโ€ข6mo ago
Hi @TomD. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- The issue you're facing is related to the fact that you're using the fetch API directly, which doesn't automatically include the authentication context that the Wasp API wrapper provides. In Wasp, the context.user is populated by the auth middleware when you make a request using the API wrapper. This is why you're not seeing the context.user when you're using fetch directly. To solve this, you can use the API wrapper provided by Wasp, which automatically includes the authentication context. Here's an example of how to use it:
import { api } from "wasp/client/api";

async function fetchCustomRoute() {
const res = await api.get("/foo/bar");
console.log(res.data);
}
import { api } from "wasp/client/api";

async function fetchCustomRoute() {
const res = await api.get("/foo/bar");
console.log(res.data);
}
(Source: Using the API from the Client (https://wasp-lang.dev/docs/advanced/apis#using-the-api-from-the-client)) However, if you need to use fetch directly for some reason (like in your case, for streaming), you'll need to manually include the authentication token in your request. The exact way to do this depends on how you've set up authentication in your app, but it usually involves setting an Authorization header with the user's token. Unfortunately, the provided knowledge sources do not include information on how to manually include the authentication token when using fetch directly. You might want to check the Wasp documentation or ask in the Wasp community for more specific guidance on this. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @TomD, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
martinsos
martinsosโ€ข6mo ago
Our expert for both Auth and streaming @miho will be best able to answer this one ๐Ÿ˜„
miho
mihoโ€ข6mo ago
If we take a look at the custom API feature reference: https://wasp-lang.dev/docs/advanced/apis#api-reference there is a auth field that if set to true will attempt to extract the user from the JWT into the context.user. The issue with using fetch in this case is that the regular request headers that we set for you by default are not set ... because we use axios for our RPC and not fetch. So, this leads me to the following: - either we use axios for the streaming request - or we set the JWT token manually for the fetch streaming request
Custom HTTP API Endpoints | Wasp
In Wasp, the default client-server interaction mechanism is through Operations. However, if you need a specific URL method/path, or a specific response, Operations may not be suitable for you. For these cases, you can use an api. Best of all, they should look and feel very familiar.
miho
mihoโ€ข6mo ago
Let me try implementing the example with Axios, and I'll report back ๐Ÿ˜„
TomD
TomDOPโ€ข6mo ago
Ok thanks for the clarification, if I just switch to axios things should work ? I'll try this then. For the second solution with the JWT token, how can I access it to pass it in the header of my request ?
miho
mihoโ€ข6mo ago
Using Axios
// In Wasp
api streamingText {
httpRoute: (GET, "/api/streaming-test"),
auth: true,
fn: import { getText } from "@src/server/streaming.js",
}
// In Wasp
api streamingText {
httpRoute: (GET, "/api/streaming-test"),
auth: true,
fn: import { getText } from "@src/server/streaming.js",
}
+
// On the client:
function useTextStream(path) {
const [response, setResponse] = useState("");
useEffect(() => {
const controller = new AbortController();
fetchStream(path, (data) => setResponse(data), controller);
return () => {
controller.abort();
};
}, []);

return {
response,
};
}

function fetchStream(path, onData, controller) {
return api.get(config.apiUrl + path, {
responseType: "stream",
cancelToken: controller.token,
onDownloadProgress: (progressEvent) => {
const xhr = progressEvent.event.target;
onData(xhr.responseText);
},
});
}
// On the client:
function useTextStream(path) {
const [response, setResponse] = useState("");
useEffect(() => {
const controller = new AbortController();
fetchStream(path, (data) => setResponse(data), controller);
return () => {
controller.abort();
};
}, []);

return {
response,
};
}

function fetchStream(path, onData, controller) {
return api.get(config.apiUrl + path, {
responseType: "stream",
cancelToken: controller.token,
onDownloadProgress: (progressEvent) => {
const xhr = progressEvent.event.target;
onData(xhr.responseText);
},
});
}
+
// In the API function:
const user = context.user?.auth?.identities[0].providerUserId ?? 'stranger'
// In the API function:
const user = context.user?.auth?.identities[0].providerUserId ?? 'stranger'
This worked for me ๐Ÿ˜„ Axios doesn't support streaming properly since it's based on XHR and that's the reason I went with fetch in the initial example. Using fetch Similar to the axios one, but you have to add the sessionId to the headers.
import { getSessionId } from "wasp/client/api";

async function fetchStream(path, onData, controller) {
const sessionId = getSessionId();
const response = await fetch(config.apiUrl + path, {
signal: controller.signal,
headers: {
Authorization: `Bearer ${sessionId}`,
},
});
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
return;
}
onData(value.toString());
}
}
import { getSessionId } from "wasp/client/api";

async function fetchStream(path, onData, controller) {
const sessionId = getSessionId();
const response = await fetch(config.apiUrl + path, {
signal: controller.signal,
headers: {
Authorization: `Bearer ${sessionId}`,
},
});
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
return;
}
onData(value.toString());
}
}
So, this get weird because Axios doesn't have a nice way of supporting streaming and Wasp is using Axios by default. On the other hand, it's weird to set the session manually like this, but it's okay since it's nothing too custom.
TomD
TomDOPโ€ข6mo ago
Indeed it's working ! I went the sessionId solution fow now to not change to axios but I will probably come back to your message and implement this clean solution if I have time ! Thanks a lot !
martinsos
martinsosโ€ข6mo ago
@miho if I am correct you didn't just use new instance of axios, but you instead imported Wasp's api which is our instance of axios that we configured to use auth and stuff, right?
miho
mihoโ€ข6mo ago
You are correct, I forgot to mention that ๐Ÿ‘ It needs to be imported from wasp/client/api
Want results from more Discord servers?
Add your server