'How to do authenticated requests with sveltekit and ssr?
I'm trying to handle an api call which can be called optionally with an Authorization header (if a user is logged in).
The idea is if the user is logged in they can get contact info back if not, then contact info is deleted.
API works fine as tested, if Auth header is not present contact info is removed.
However due to using sveltekit ssr, I need to either make an optional authenticated request or not (ie: Googlebot friendly).
This is the svelte code:
<script context="module">
import User from '$api/user';
import Jobs from '$api/jobs';
import Searches from '$api/searches';
export async function load({ fetch, params }) {
const { username } = params;
const user = await new User(fetch).getByUsername(username);
const jobs = await new Jobs(fetch).getByUsername(username);
const searches = await new Searches(fetch).getByUsername(username);
return {
props: {
jobs,
user,
searches,
username
}
};
}
</script>
<script>
import { fromNow } from '$lib/dates';
import { onMount } from 'svelte';
export let user = {};
export let username = null;
export let jobs = [];
export let searches = [];
onMount(async () => {
user = await new User().getByUsername(username);
console.log(user);
});
</script>
The fetch from ssr does not support AUthorization header and I'm not sure how to add it.
The fetch from browser mode does add it becuase it gets it from localStorage.
Here is my $lib/create-api.js file:
import { browser } from '$app/env';
class Api {
constructor(fetch) {
this.fetch = browser ? fetch.bind(window) : fetch;
}
async api(path, body = {}, opts = {}) {
path = import.meta.env.VITE_API_ENDPOINT + path;
body = JSON.stringify(body);
const method = opts.method || 'GET';
const headers = {};
if (browser) {
const token = localStorage.getItem('token');
headers.Authorization = token ? 'Bearer ' + token : '';
}
try {
const res = await this.fetch(path, {
method: opts.method || 'GET',
body: method === 'GET' ? null : body,
headers
});
if (res.ok) {
return await (opts.raw ? res.text() : res.json());
}
const err = await res.json();
throw new Error(err.message);
} catch (err) {
throw new Error(err);
}
}
}
export default Api;
The constructor checks if fetch is passed or not (in the case of ssr).
The issue is I get a 500 either way. Not sure why.
FetchError: request to http://localhost:3001/api/1/users/chovy failed, reason: connect ECONNREFUSED ::1:3001
it should not give a 500 because API returns an object regardless of whether logged in or not.
$ curl http://localhost:3001/api/1/users/chovy -s | jq .
{
"username": "chovy",
"created_at": "2021-11-01T03:49:59.943Z",
"contactme": 1,
"location": "United States of America",
"stripe_customer_id": null
}
➜ grazil
no auth above.
With bearer:
$ curl -H 'Authorization: Bearer abc' -s http://localhost:3001/api/1/users/chovy | jq .
{
"username": "chovy",
"email": "[email protected]",
"created_at": "2021-11-01T03:49:59.943Z",
"contactme": 1,
"phone": "xxx",
"location": "United States of America",
"stripe_customer_id": null
}
...so API works as intended but SSR is throwing 500 for some reason.
Solution 1:[1]
So I had a small bug in create-api.js
Changing this:
class Api {
constructor(fetch) {
this.fetch = browser ? fetch.bind(window) : fetch;
}
...
}
To this....fixes the problem:
class Api {
constructor(ssrFetch = null) {
this.fetch = typeof window !== 'undefined' ? fetch.bind(window) : ssrFetch;
}
...
}
There was a naming collision with calling the passed in "fetch" from ssr the name "fetch". If I changed the passed in variable to "ssrFetch" it works.
I also checked for window before binding which should be default behavior with onMount() client side calls.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | chovy |
