res.redirect in API route NextJS gives a loop - next.js

I have this code in my /api/[verificationToken] which is when accessed by the user, the verification token will be updated. For now, I am trying to check if the token exists in the database and corresponds to a registered email.
import prisma from "../../../lib/prisma";
export default async function handler(req, res) {
const token = req.query;
const findEmail = await prisma.user.findFirst({
where: {
token: token.verificationToken,
},
});
if (findEmail) {
console.log("email exists");
} else {
console.log("email doesn't exist");
return res.redirect("/auth/login");
}
}
The problem is, when I go to http://localhost:3000/auth/api/nonexistenttoken, "email doesn't exist" displays in a loop. I have also tried
res.writeHead(302, {
Location: '/auth/login'
});
res.end();
But it still gives me the same loop. What I want to happen is that when the token doesn't exist (which also means the email also doesn't), it should redirect the user to the login page.

Related

How to implement iron-session with session id

I am using iron-session, next-connect with nextjs in our webapp and one of the requirements is to publish analytics events from our frontend code, like page views, button clicks and other custom events. These events are stored in our database and used by our data analyst with PowerBI.
Our webapp takes a user on an onboarding journey, then once it's done, we create an account for the user and redirects to dashboard. For the onboarding part, we don't have a user id yet while in the dashboard, we already do. However, we want to be able to track the user journey in the webapp so we need an identifier that is persisted throughout the whole journey. Thus, we think of a session id with the iron-session.
Now iron-session doesn't have a concept of session id, so I am trying to implement it myself. The session id will be our identifier of the user in our events table.
Here is the withSession middleware used with next-connect
import { getIronSession } from "iron-session";
import type { IncomingMessage } from "http";
import type { NextApiRequest } from "next";
import { nanoid } from "nanoid";
import appConfig from "#/backend/app.config";
export const sessionOptions = {
password: appConfig.secret,
cookieName: appConfig.cookies.sessionToken.name,
cookieOptions: appConfig.cookies.sessionToken.options,
};
export async function withSession(
req: IncomingMessage | NextApiRequest,
res: any,
next: any
) {
const session = await getIronSession(req, res, sessionOptions);
if (!session.id) session.id = nanoid(32);
req.session = session;
await req.session.save();
return next();
}
declare module "iron-session" {
interface IronSessionData {
user?: { id: string };
id: string;
}
}
And a route that will use the middleware
const router = createRouter<NextApiRequest, NextApiResponse>()
.use(...([withSession, withLogger, withTenant] as const))
.get(async (req, res) => {
// Authenticate user
req.session.user = { id: userId };
await req.session.save();
return res.redirect("/");
});
export default router.handler();
Is this a correct implementation of the said requirement?
Some libraries implement a kind of session.regenerate() when a user perform signIn and signOut. Do I need to implement it too? If I do, I will lose the identifier that persists throughout the whole user journey.
since you are using typescript first define the type of session object
declare module "iron-session" {
interface IronSessionData {
nameOfSessionObject?: {
// in your implementation you were creating req.user and req.id
// you could overwrite the req properties
user?: { id: string };
// you can manually create on the server
id: string;
};
}
}
create a wrapper session function
export function withSession(handler: any) {
return withIronSessionApiRoute(handler, {
password: appConfig.secret,
cookieName: appConfig.cookies.sessionToken.name,
// Said in another way, the browser will not send a cookie with the secure attribute set over an unencrypted HTTP request
cookieOptions: appConfig.cookies.sessionToken.options,
})}
create the session object. you do not use getIronSession when creating a session.
you need that when you need to access to the session object in middleware
export default withSessio(
async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "GET") {
try {
const sessionObj={....}
req.session.nameOfSessionObject={...sessionObj}
await req.session.save();
// whatever you want to return
return res.json(sessionObj);
} catch (error) {
console.error("error in verify post req", error);
// 422 Unprocessable Entity
res.status(422).send({ message: "Cannot create SESSION" });
}
} else if (req.method === "POST") {
try {
..HANDLE POST HERE
} catch (error) {
res.status(422).send({ message: "Cannot generate a SESSION" });
}
} else {
return res.status(200).json({ message: "Invalid api Route" });
}
}
);
now you can import above handler and connect with next-connect

Fetch protected api routes from outside of Next Js website

I have setup the following endpoint i under pages/api in my next js app.
export default async (req: NextApiRequest, res:NextApiResponse<ResponseData>) => {
const user = await getSession({ req });
await dbConnect();
if (!user) {
return res.json({ error: "not logged in" });
}
if (req.method === "POST") {
// save to database
}
res.end();
};
I want to fetch that endpoint outside of the app (from a different website), however, it keeps giving me "not logged in" error. Is there a way to fix it ?

How to getItems from localStorage saved by another process in Nuxt 3?

I have been trying to develop an application in Nuxt 3, and i am very new to this Meta-Framework. While a user logs in, the API sends token in response and it is saved to localStorage using if(process.client), however once the login is completed, i need my homepage to get the token stored in localstorage and now i am getting LocalStorage undefined error on build. Below is my code for setItems and getItems
code for setItems
methods: {
async handleSubmit() {
const response = await axios.post('http://localhost:8000/api/login', { email: this.email, password: this.password })
console.log(response);
notify({ title: response })
if (process.client) {
localStorage.setItem('token', response.data.token)
}
this.$router.push('/')
}
}
getItems code
async created() {
const response = await axios.get('http://localhost:8000/api/user', {
headers: {
Authorization: 'Bearer' + localStorage.getItem('token')
}
})
console.log(response)
},
process.client should probably not be needed here, because handleSubmit will probably be called upon a click or a user interaction.
Meanwhile, you could use process.client in your second snippet because created lifecycle hook is ran on both server and client side. And there is no such thing as localStorage in Node.js.
Or you could use mounted, which is only called client-side.

signInWithEmailAndPassword: getting auth/user-token-expired [duplicate]

I am using Firebase authentication in my iOS app. Is there any way in Firebase when user login my app with Firebase then logout that user all other devices(sessions)? Can I do that with Firebase admin SDK?
When i had this issue i resolved it with cloud functions
Please visit this link for more details https://firebase.google.com/docs/auth/admin/manage-sessions#revoke_refresh_tokens
Do the following;
Set up web server with firebase cloud functions (if none exists)
use the admin sdk(thats the only way this method would work) - [Visit this link] (
(https://firebase.google.com/docs/admin/setup#initialize_the_sdk).
Create an api that receives the uid and revokes current sessions as specified in the first link above
admin.auth().revokeRefreshTokens(uid)
.then(() => {
return admin.auth().getUser(uid);
})
.then((userRecord) => {
return new Date(userRecord.tokensValidAfterTime).getTime() / 1000;
})
.then((timestamp) => {
//return valid response to ios app to continue the user's login process
});
Voila users logged out. I hope this gives insight into resolving the issue
Firebase doesn't provide such feature. You need to manage it yourself.
Here is the Firebase Doc and they haven't mentioned anything related to single user sign in.
Here is what you can do for this-
Take one token in User node (Where you save user's other data) in Firebase database and regenerate it every time you logged in into application, Match this token with already logged in user's token (Which is saved locally) in appDidBecomeActive and appDidFinishLaunching or possibly each time you perform any operation with Firebase or may be in some fixed time interval. If tokens are different logged out the user manually and take user to authenticate screen.
What i have done is:
Created collection in firestore called "activeSessions".User email as an id for object and "activeID" field for holding most recent session id.
in sign in page code:
Generating id for a user session every time user is logging in.
Add this id to localstorage(should be cleaned everytime before adding).
Replace "activeID" by generated id in collection "activeSessions" with current user email.
function addToActiveSession() {
var sesID = gen();
var db = firebase.firestore();
localStorage.setItem('userID', sesID);
db.collection("activeSessions").doc(firebase.auth().currentUser.email).set({
activeID: sesID
}).catch(function (error) {
console.error("Error writing document: ", error);
});
}
function gen() {
var buf = new Uint8Array(1);
window.crypto.getRandomValues(buf);
return buf[0];
}
function signin(){
firebase.auth().signInWithEmailAndPassword(email, password).then(function (user) {
localStorage.clear();
addToActiveSession();
}
}), function (error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
alert('wrong pass');
} else {
alert(errorMessage);
}
console.log(error);
};
}
Then i am checking on each page if the id session in local storage is the same as "activeID" in firestore,if not then log out.
function checkSession(){
var db = firebase.firestore();
var docRef = db.collection("activeSessions").doc(firebase.auth().currentUser.email);
docRef.get().then(function (doc) {
alert(doc.data().activeID);
alert(localStorage.getItem('userID'));
if (doc.data().activeID != localStorage.getItem('userID')) {
alert("bie bie");
firebase.auth().signOut().then(() => {
window.location.href = "signin.html";
}).catch((error) => {
// An error happened.
});
window.location.href = "accountone.html";
} else{alert("vse ok");}
}).catch(function (error) {
console.log("Error getting document:", error);
});
}
PS: window has to be refreshed to log inactive session out.

OAuth2 fails to return auth token using simple-oauth2 and Firebase Functions for Spotify Authentication

I have been working on a oauth2 flow for spotify by following this similar tutorial by the Firebase team for Instagram HERE
I am able to submit my credentials and return the user code and state in the url, but when I run the method to submit the code to return an auth token, the auth token that I print to console in the Firebase functions returns: Auth Token Error Not Found. Here's my workflow:
Here's the Spotify docs
FIRST, I have a function to configure my spotifyOAuth:
function spotifyOAuth2Client() {
// Spotify OAuth 2 setup
const credentials = {
client: {
id: functions.config().spotify.clientid,
secret: functions.config().spotify.clientsecret,
},
auth: {
tokenHost: 'https://accounts.spotify.com',
authorizePath: '/authorize'
},
};
return require('simple-oauth2').create(credentials);
}
I use that function in this Firebase function that is called using https://us-central1-<my project string>.cloudfunctions.net/redirect:
exports.redirect = functions.https.onRequest((req, res) => {
const oauth2 = spotifyOAuth2Client();
cookieParser()(req, res, () => {
const state = req.cookies.state || crypto.randomBytes(20).toString('hex');
console.log('Setting verification state:', state);
res.cookie('state', state.toString(), {
maxAge: 3600000,
secure: true,
httpOnly: true,
});
const redirectUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI,
//scope: OAUTH_SCOPES,
state: state,
});
console.log('Redirecting to:', redirectUri);
res.redirect(redirectUri);
});
});
The code above returns a url string with the proper parameters, the following code block is where my code breaks, I have another cloud function that runs after being redirected from the res.redirect(redirectUri) above. And when I try to run the getToken() method, it appears to not return anything because I hit the catch block instead? This is where I observe the Auth Token Error Not Found.
const oauth2 = spotifyOAuth2Client();
try {
return cookieParser()(req, res, async () => {
console.log('Received verification state:', req.cookies.state);
console.log('Received state:', req.query.state);
if (!req.cookies.state) {
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
} else if (req.cookies.state !== req.query.state) {
throw new Error('State validation failed');
}
console.log('Received auth code:', req.query.code);
console.log(OAUTH_REDIRECT_URI);
// Get the access token object (the authorization code is given from the previous step).
const tokenConfig = {
code: req.query.code,
redirect_uri: 'http://localhost:8100/popup'
};
// Save the access token
try {
const result = await oauth2.authorizationCode.getToken(tokenConfig)
const accessToken = oauth2.accessToken.create(result);
console.log('inside try');
console.log(result);
console.log(accessToken);
} catch (error) {
console.log('Access Token Error', error.message);
}
I've double checked my spotify client/secret credentials in the config, what is going wrong with this OAuth2 flow?
Resolved my issue, I was not using the correct endpoints:
const credentials = {
client: {
id: functions.config().spotify.clientid,
secret: functions.config().spotify.clientsecret,
},
auth: {
tokenHost: 'https://accounts.spotify.com',
authorizePath: '/authorize',
tokenPath: '/api/token'
},
};

Resources