SvelteKit Hook Prevents Endpoint Request - javascript

Trying out SvelteKit and I'm having a hard time with hooks. The docs don't really seem to explain it all too well. My current understanding of hooks is that they basically allow you to interact with requests made to your server before they get to their destination? (I'm open to a better explanation - specifically the handle hook).
My current issue is I made an endpoint called login. As the name suggests, it allows users to sign into my application by generating a token and storing it as a cookie on their client. This works until I add hooks. After reading the hooks description, I figured the handle hook is perfect for what I want to do - validate the token on each request - if invalid, reroute the user to the login screen, if valid, allow the request to continue uninterrupted.
export const handle: Handle = async ({ event, resolve }) => {
const isLogin = event.url.pathname.startsWith('/login')
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
const token = cookies['token']
if (!token) {
if (!isLogin) {
return Response.redirect(`${event.url.origin}/login`)
}
return await resolve(event)
} else {
try {
await verifyToken(token)
if (isLogin) {
return Response.redirect(`${event.url.origin}/about`)
}
} catch (err) {
return Response.redirect(`${event.url.origin}/login`)
}
}
return await resolve(event)
};
This does not work as expected. When I initiate the request to the api/login endpoint, the request does not seem to make it there. I have console.logs all over the endpoint but no messages were outputted to the terminal & when I check the application storage, no new cookie was added.
What am I missing about hooks?
Why is it not passing the request off to the endpoint?
Any idea how I can fix this?

The handle hook runs for every request—including endpoints.
When you fetch /api/login without a token, your hook will redirect the request to /login since isLogin === false. You need to allow through every route that should be accessible without a login, for example:
const isLogin = /^\/(api\/)?login$/.test(event.url.pathname)

Related

Next.js redirect from an API route

I am building a back-office app that requires users to sign in.
I have 2 external APIs:
API A : to manage user accounts and sessions
API B : to perform CRUD actions on another database (unrelated to users database)
The problem is that I don't want users to be able to perform calls to API B if their session is not valid. So I added some API endpoints in Next (under pages/api) that do the following actions:
verifying the validity of the session against API A
if session is valid: continue to step 3, if not: redirect to page /login
make the call to API B
Everything works fine if the session is valid but it fails if the session is not valid.
I have tried
res.redirect(307, '/login').end()
and
res.writeHead(307, { Location: '/login' }).end()
but it didn't work. It fails even by specifying the whole path (http://localhost:3000/login). What I don't understand is that I am successfully redirected to my /login page if I make the request directly from the browser (GET http://localhost:3000/api/data). It doesn't work when I make the request with Axios inside a React component.
Any idea how I can fix this?
As #juliomalves and #yqlim explained, I had to make the redirect manually based on the response of the API.
Faced same problem solve using below code:
Api
res.status(200).json({ success: "success" }) //add at last of the api to give response
page
import Router from 'next/router'
let res = await fetch('api', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
if (res.status == 200) {
Router.push('/location')
}
Answer is correct as #Jules Grenier sayes,but provided an example
You do not need .end(). Have you tried res.redirect(307, '/login')?
In Next.js v12 and v13, the following works for me.
// /api/example.js
const handler = async function (req, res) {
// custom logic
if (failed)
return res.redirect(307, '/login')
}
export default handler;
The API request must be initiated by a <form>.
redirect will not work with <fetch>

Next.js handle 0Auth authorization with MoneyButton

I am attempting to implement 0Auth user authorization for my Next.js app using MoneyButton API. I am able to trigger the authorization request with client.requestAuthorization('auth.user_identity:read','http://localhost:3000');
And it works smoothly redirecting me to MoneyButton permission consent and back to my app with the code and state params in URL -> ?code=6aa72eef702eb710cd22715d797cf7d27e06532a&state=38984b9d-3af0-48f1-8b5f-3fa47f4dfd9d
There is client.handleAuthorizationResponse(); method for handle the response. That method automatically gets the tokens from the query parameters and set the internal state of the client to use them. Also it saves the credentials in local storage, so the user stays logged in with Money Button if they close the browser.
But unfortunately i don't know how to use this method after being redirected back to my app. I am using it in Authuser function, but requestAuthorization triggers redirect to moneybutton, so rest of the function is not executed. How to use handleAuthorization after being redirected back to application?
https://docs.moneybutton.com/docs/api/auth/api-auth-jsclient.html - here are the MoneyButton docs
I am also considering to add MoneyButton as custom 0Auth provider in NextAuth.js to make integrations faster in the future.
Authuser.js
const { MoneyButtonClient } = require('#moneybutton/api-client')
export default function Authuser () {
const client = new MoneyButtonClient('MYAPP_OAUTH_IDENTIFIER_CODE');
client.requestAuthorization('auth.user_identity:read','http://localhost:3000');
client.handleAuthorizationResponse();
const refreshToken = client.getRefreshToken();
client.setRefreshToken(refreshToken)
}
You need to make sure that client.handleAuthorizationResponse(); is run client side (not server side render) after the moneybutton auth has redirected back:
if ((new URLSearchParams(window.location.search)).has('code')) {
await client.handleAuthorizationResponse()
const accessToken = await client.getValidAccessToken()
...
}

How to verify a user's ID token in firestore using Javascript?

I am building a react native application and am using Firebase, more specifically firestore, in order to manage my data. My current objective is to implement an auto login feature on my app, where if the user exits the app, I want them to stay signed in, unless they manually hit the Sign Out button before exiting the app. Here is my current process of doing this:
When the user logs into the app, I sign them in by:
firebase.auth().signInWithEmailAndPassword(email, password).
I then get their idToken by:
let authIdToken = "";
firebase
.auth()
.currentUser.getIdToken(true)
.then(function (idToken) {
authIdToken = idToken
})
.catch(function (error) {
console.log(error)
});
I then want to save this token into the phone, so when the user opens the app again, I can fetch this token and check its validity. If it is valid, then I can log the user in using their idToken. In react native, I can do this by doing:
AsyncStorage.setItem(
"userData",
JSON.stringify({
token: token,
})
);
Now when the app loads up:
const startScreen = props => {
useEffect(() => {
const tryLogin = async () => {
const userData = await AsyncStorage.getItem("userData");
const transformedData = JSON.parse(userData);
const { token } = transformedData;
await firebase
.auth()
.verifyIdToken(token, true)
.then((payload) => {
console.log(true)
})
.catch((error) => {
if (error.code == "auth/id-token-revoked") {
// Token has been revoked. Inform the user to reauthenticate or signOut() the user.
console.log("revoked")
} else {
console.log("error")
}
});
};
tryLogin();
}, []);
The Issue: When I try to verify the token this way, I am met with the following error: firebase.auth().verifyIdToken is not a function.
I read through the documentation and am unsure of how else to verify this token using JS. How do I verify it? Let me know if my verification process is incorrect and how it should be done. I am new to using firestore and doing authentication in general and hope to learn how to do it the right way.
Another helpful note: This is how I am configuring my firestore: !firebase.apps.length ? firebase.initializeApp(firebaseConfig) : {};
Thanks!
I then want to save this token into the phone, so when the user opens the app again, I can fetch this token and check its validity.
This is completely unnecessary. Firebase Auth with persist the signed in user, and automatically refresh the token without you having to do anything. All you need to do is listen to when updates to the token are made available, and act on the new token as needed. You can establish an ID token listener using onIdTokenChanged as shown in the linked API documentation:
firebase.auth().onIdTokenChanged(function(user) {
if (user) {
// User is signed in or token was refreshed.
}
});
Once you have this token, you know that the user is successfully signed in. There is nothing left to do. There is no need to use it to sign in.
Also, you can't verify the token on the frontend. The verifyIdToken method you're looking at is for the Admin SDK only, which only runs on the backend. The idea is that you get the token on the fronend, then pass it to the backend as described in the documentation for the Admin SDK. The backend uses this to securely determine if the user on the frontend is who they say they are.
Since you didn't say if you have a backend or not, dealing with this token might not be necessary at all. If you just want to know when the user is signed in (even if they are just returning to the page after being away, then you can skip everything above and just use an auth state observer. Again, Firebase Auth persists information about the user so you don't have to sign them in again. The observer will tell you when the automatic sign-in is complete, or if they are not signed in at all.

authentication conflict between sessions in Express server

I am playing around with this library and I am experiencing an annoying scenario which I believe comes from some sort of conflict in cookies or headers authentication.
When I login to one account everything works great. But then when trying to login to another account, it simply ignore the new data provided and move through the authentication with the old data and connecting to the old account. No matter if the email or the password even exist. (Tried also with fake data).
The library doesn't have proper logout method which make sense, you dont really need one because when you run it simply using node on your machine without any server involved and there is no cookies or any kind of data in memory, everything work great. I can login to as many account as I want.
The problem is when running it on an Express server.
CODE:
// api.js
const OKCupid = require("./okc_lib");
const Promise = require("bluebird");
const okc = Promise.promisifyAll(new OKCupid());
async function start(req, res, next) {
const {
body: {
username,
password
}
} = req;
try {
await okc.loginAsync(username, password);
okc.search(
searchOpt,
(err, resp, body) => {
if (err) return console.log({ err });
const results = body.data;
// do dsomething with results
return res.status(200).json({ message: "OK" });
});
}
);
} catch (error) {
res.status(400).json({ message: "Something went wrong", error });
}
}
module.exports = { start };
// routes.js
const express = require("express");
const router = express.Router();
const { start, login } = require("../actions/okc");
router.post("/login", login);
router.post("/start", start);
module.exports = router;
So when trying first to post to url/login it works fine. But when you try to do it again with different username and password it simply go through and ignore the new data and connect to the old one.
As part of my investigation I looked at the source code of the library and found a method clearOAuthToken which clear the token from the header. However it didn't really do anything. So I tried to remove the jar initialisation from the requester helper and it was the only thing that helped me to move on and login to another account. BUT it was only for experimenting and cant be a solution as you do need those cookies for other parts of the library. It was only a proof the problem isn't in the headers but in the cookies.
Any idea how can I "reset" state of server between each call?
"when trying to login to another account, it simply ignore the new data provided and move through the authentication with the old data and connecting to the old account."
As OP mentioned in the comment, this is not an authorization header issue, but a cookie issue.
To implement the logout interface, you can manually clear the cookies:
OKCupid.prototype.logout = function(){
request = request.defaults({jar: request.jar()}) // reset the cookie jar
headers.clearOAuthToken(); // just in case
}

How can I store a JWT in cookies using Axios?

I am using JWT in my React application and Axios to handle API calls. I am looking for a way to store the token in cookies so that I am not redirected to login again every time the browser is refreshed.
Here is my setup for Axios and my login call:
let authToken = null;
const axios = axiosAPI.create({
baseURL: baseURL
});
// User login
export const loginUser = (data) => {
return new Promise((resolve, reject) => {
axios.post(`${baseURL}/jwt-auth/v1/token`, data)
.then((res) => {
authToken = res.data.token;
// Adds the token to the header
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
I am not certain where I should be setting the cookie and how to set it?
EDIT:
I have rewritten my code using js-cookie so it looks like the comment.
import axiosAPI from 'axios';
import Cookies from 'js-cookie';
let authToken = null;
const axios = axiosAPI.create({
baseURL: `${baseURL}`
});
// Check if user is logged in.
(function () {
if (Cookies.get('token') === null) {
// This means that there's no JWT and no user is logged in.
axios.defaults.headers.common.Authorization = null;
} else {
// This means that there's a JWT so someone must be logged in.
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
}
}());
// User login
export const loginUser = (data) => {
return new Promise((resolve, reject) => {
axios.post(`${baseURL}/jwt-auth/v1/token`, data)
.then((res) => {
authToken = res.data.token;
Cookies.setToken('token', authToken);
// Adds the token to the header
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
However this prevents me from logging in at all and I get the error 'wrong number of segments'. Any idea why this isn't working?
There are a few different options that you can take here to solve the problem you are having, which is simply finding somewhere to store the JWT so that you can use it even after you refresh the page.
Save the JWT in localStorage or sessionStorage in your axios.post callback so that you can have access to it even after the page refreshes. To learn which storage is most suitable for your app, see this.
To keep it short, values stored in localStorage will persist until you explicitly delete them (you can do this via your JS code). Also, any tabs you open in your browser to that domain will have access to this (very useful if you want to still be logged in with the new tabs). On the other hand, values stored in sessionStorage only live until the tab is closed. They can't be shared across tabs either.
Using this is as simple as:
localStorage.setItem("JWT", authToken); or sessionStorage.setItem("JWT", authToken); in your callback after your authToken = res.data.token;
So now that you have a place where you have stored the JWT, all you need to do is make sure to check if a JWT exists in storage when your app initializes on page load (or refresh). Here's a basic example of how to use localStorage:
// This should be one of the first things that run on your app.
const axios = axiosAPI.create({
baseURL: baseURL
});
// Check if user is logged in.
(function() {
let authToken = localStorage.getItem("JWT");
if (authToken === null) {
// This means that there ISN'T JWT and no user is logged in.
axios.defaults.headers.common.Authorization = null;
} else {
// This means that there IS a JWT so someone must be logged in.
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
}
})();
This will make sure that the user is not logged out on page load, if previously logged in.
Save the JWT in a client side cookie. Here, the cookie is being used as a storage mechanism since you are not actually working with server side cookies given that your authentication is all build around JWT. You can follow the same code pattern as above but instead will be using document.cookie = "key=value" to set a cookie and document.cookie to view all cookies.
This second way is less common because it forces you to do a lot of manual labor like parsing through all the cookies and making sure to set the proper cookie path attribute so that the cookie only gets sent up for the required endpoints (otherwise you're just creating unnecessary overhead). If you exercise this option, read this to help you create your cookie to fit your needs. You can also use a helper JS library like js-cookie to help manipulate client side cookies.
Additionally, I would read through https://stackoverflow.com/a/40376819/11048825 to dive further into these two options and understand the pros and cons associated with each.

Categories