Next.js with SSR: How to access cookies to retrieve a JWT and then make a post request with it - post

Explanation of my problem
So I am currently on a project of my traineeship that requires me to make a small app using Redux to manage the state of the app itself and TanStack Query (a.k.a. React Query v4) to manage asynchronous data from API calls
I am currently using Next.js with Server-Side Rendering (SSR)
And I have a big issue at the moment with one of the pages
So when the user logs in, they're redirected to their profile page, and I want to make a post request with the JSON Web Token stored in cookies to retrieve their info such as their first name and last name
What I did
I used the Next.js function getServerSideProps to make a POST request with the JWT, but because my app uses SSR. I do not have access to cookies and the JWT is stored inside cookies, but I cannot have access to client-side information until the component is fully mounted, also the app crashes when reloading because of that
I used the native React hook useEffect, I declared the variable with the value of the JWT since now I have access to the cookies, but now I cannot use the TanStack Query hook useMutation because you cannot use hooks inside other hooks
(Current implementation) I used a similar approach, but this time declared the JWT variable as undefined along with the constant using the useMutation outside the useEffect and added the JWT variable inside the array of dependencies of the hook, then I retrieved changed the value of the variable to contain the JWT and made a POST request but the post request fails
Here's the code of my current implementation
//React
import { useEffect, useState } from "react";
//Next
import Head from "next/head";
import { NextRouter, useRouter } from "next/router";
//Components
import AccountCard from "#/components/AccountCard/AccountCard";
import Button from "../../components/Button/Button";
//Utils
import { log } from "../../utils/functions/helper-functions";
import { savingsData } from "../../utils/variables/savings-data";
import CookieService from "#/utils/services/cookies.service";
import ApiService from "#/utils/services/api.service";
//Redux
//React-Redux
import { useSelector } from "react-redux";
import { useMutation } from "#tanstack/react-query";
//This is the page of the user
/**
* User page
*
* Route: `/profile/`
* */
export default function Profile(): JSX.Element {
// log({ jsonWebToken });
//We IMPORT the value of the logging state of the user when logging in
const userIsLoggedIn: boolean = useSelector((state: any) => {
return state.isLoggedIn;
});
//We're going to use the router hook to get the current to redirect the user
//if they're not logged in
const router: NextRouter = useRouter();
[...]
let jsonWebToken: string | undefined = undefined;
//We make the POST request
const apiService: ApiService = new ApiService();
const userProfileMutation = useMutation({
mutationFn: (jwt: string) => {
return apiService.postProfile(jwt);
},
onMutate: () => {},
onSuccess: (data, variables) => {
log("SUCCESS, USER INFOS:", data);
},
onError: () => {
log("FAILED TO RETRIEVE USER INFOS");
},
});
//We cannot use the push() method of the router to redirect the user to the sign-in page
//if the user isn't logged in because of the SSR (push is client side only)
useEffect(() => {
//If the user isn't logged in we redirect them to the sign-in page
const userIsNotLoggedIn: boolean = !userIsLoggedIn;
if (userIsNotLoggedIn) {
router.push("/sign-in/");
}
//We recover the jwt inside the browser"s cookies
const cookieCreator: CookieService = new CookieService();
//We initialise it
jsonWebToken = cookieCreator.getCookieByName("jwt")?.value;
log(jsonWebToken);
//#ts-ignore
userProfileMutation.mutate(jsonWebToken);
}, [jsonWebToken]);
return (
<>...</>
);
}
So I'd be very grateful if somebody was able to help me with this issue

Related

SvelteKit: Change UI on incoming Post Request

Assume the following:
src/route/create/+server.ts:
import { json } from "#sveltejs/kit";
import type { RequestHandler } from "./$types";
import { testStore } from "$lib/stores/testStore";
export const POST = (async ({ request }) => {
console.log("POST REQUEST INCOMING");
testStore.set("something") << THIS IS NOT WORKING AS EXPECTED
return json({ message: "hi" });
}) satisfies RequestHandler;
Then, in some svelte component:
testStore.subscribe((value) => {
if (value) {
console.log("Post request came in!");
}
});
When I perform a post request, I can see this part "POST REQUEST INCOMING", but the value of the store is not being updated.
In general, I want to do this: The client makes a request to some other Python backend. That backend does something which takes maybe 10 seconds. Once Python is done, it sends a POST request back to the client (the /create endpoint from above), basically saying that the process is complete. Then the client is supposed to change its UI, notifying the user.
Any idea how I can do this?
I've already tried the code above, which didn't work...

React Native Firebase onAuthStateChanged returns user after signOut

I'm struggling with signing out in my app. I'm using freshly installed managed Expo app with dev-client and #react-natice-firebase/auth, all in their current version. For the user state i'm using Recoil. Sign in works just fine, the problem occurs when signing out. Here is my AuthProvider.ts
import React, { FC, Fragment, useEffect } from 'react';
import auth, { FirebaseAuthTypes } from '#react-native-firebase/auth';
import { useSetRecoilState } from 'recoil';
import { firebaseUser } from '../../../state/user/atoms/firebaseUser';
export const AuthProvider: FC = ({ children }) => {
const setUser = useSetRecoilState(firebaseUser);
const onAuthStateChanged = (user: FirebaseAuthTypes.User | null) => setUser(user);
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber;
}, []);
return <Fragment children={children} />;
};
And the function I use to sign out:
const logout = () => async () => await auth().signOut()
<MainButton action={logout()} label={'Logout'} />
After sign in the onAuthStateChanged listener returns user object, but after hitting 'Logout' button it returns the user object again instead of null and the interface doesn't update. After hitting it again, console returns an error:
[Unhandled promise rejection: Error: [auth/no-current-user] No user currently signed in.]
onAuthStateChanged is not called again, user state persists and the interface doesn't update.
User state is cleared after restarting the app and the sign in works just fine again. The behaviour is similar when switching users (eg. from anonymous to signed in).
In your useEffect, you need to actually call the 'unsubscribe' function provided by useAuthStateChanged.
useEffect(() => {
const unsubscribe = auth().onAuthStateChanged(onAuthStateChanged);
return () => unsubscribe();
}, []);
Maybe someone will face the same problem in the future, so here's what was causing it. It wa Recoil, apparently the two libraries don't work well together, so I've rewritten it using context and works just fine.

Get JWT token in NextJS API

I created a NextJS application integrated with Amazon Cognito. I have a landing page that is using the Amplify Auth API (not the components). Now I need to call an external API to do CRUD operations. What's the best way to do this in NextJS?
I'm thinking I'll create an API in NextJS that will forward the request to the actual external REST API. But my problem is I'm not able to get the JWT Token on the API, since it's a backend code.
A code like this:
Auth.currentSession().then(data => console.log(data.accessToken.jwtToken));
Obviously won't work:
[DEBUG] 20:42.706 AuthClass - Getting current session
[DEBUG] 20:42.706 AuthClass - Failed to get user from user pool
[DEBUG] 20:42.707 AuthClass - Failed to get the current user No current user
(node:20724) UnhandledPromiseRejectionWarning: No current user
How can I get the token in the API?
I have resolved this problem by using the aws-cognito-next library.
Following the documentation from https://www.npmjs.com/package/aws-cognito-next, I have created an auth utility:
import { createGetServerSideAuth, createUseAuth } from "aws-cognito-next";
import pems from "../../pems.json"
// create functions by passing pems
export const getServerSideAuth = createGetServerSideAuth({ pems });
export const useAuth = createUseAuth({ pems });
// reexport functions from aws-cognito-next
export * from "aws-cognito-next";
The pem file was generated by issuing the command (needless to say, you must configure an Amazon Cognito service first):
yarn prepare-pems --region <region> --userPoolId <userPoolId>
And finally, in the NextJs API:
import {getServerSideAuth} from "../../src/utils/AuthUtils"
export default async (req, res) => {
const initialAuth = getServerSideAuth(req)
console.log("initialAuth ", initialAuth)
if (initialAuth) {
res.status(200).json({status: 'success'})
} else {
res.status(400).json({status: 'fail'})
}
}
A simple method is to enable ssrContext in your app and Amplify will provide the user credentials to your api
on the frontend eg _app.tsx (or app.js)
import Amplify, { Auth, API } from "aws-amplify";
import awsconfig from "../src/aws-exports";
Amplify.configure({...awsconfig, ssr: true});
Then in the api you can simply get the currently authenticated cognito user
eg api/myfunction.tsx (or js)
import Amplify, { withSSRContext } from "aws-amplify";
import awsExports from "../../src/aws-exports";
Amplify.configure({ ...awsExports, ssr: true });
/* #returns <CognitoUser>,OR null if not authenticated */
const fetchAuthenticatedUser: any = async (req: NextApiRequest) => {
const { Auth } = withSSRContext({ req });
try {
let user = await Auth.currentAuthenticatedUser();
return user;
} catch (err) {
return null;
}
}

Plaid Link Javascript

I am integrating with Plaid Link and Stripe into a wizard form flow (many pages with many fields on each page). All information that the user enters is stored in a global variable "this.applicationservice.application.applicant". The user hits a payment verification in the middle of this flow, where Plaid pops an IFrame after calling Plaid.create(plaidparameters).open(). When Plaid is initialized it wipes my browser memory and "this.applicationservice.application.applicant" is now undefined.
How can I avoid losing the browser memory when calling the Plaid Initialization?
this.http.post('https://localhost:8080/v1/validateach').subscribe(
response => {
let plaidparameters = {
token: response.linkToken,
onSuccess: function(public_token, metadata) {
// Memory is wiped
console.log(this.applicationservice.application)
}
};
// Works Fine
console.log(this.applicationservice.application)
Plaid.create(plaidparameters).open();
}
);
So the problem is that the scope of this inside the onSuccess callback doesn't extend outside the callback.
One of my colleagues who works a lot more on JavaScript suggested the following:
this.http.post('https://localhost:8080/v1/validateach').subscribe(
response => {
const onSuccess = (public_token, metadata) => {
console.log(this.applicationservice.application);
};
let plaidparameters = {
token: response.linkToken,
onSuccess,
};
// Works Fine
console.log(this.applicationservice.application)
Plaid.create(plaidparameters).open();
}
);
And also added: It might actually be better to define that callback function outside of the invocation of this.http.post so it doesn’t inherit its scope from the plaidparameters object.

Request from Ember front to Rails back is not happening

I am implementing a front-end in ember 1.13 with a Rails back-end and having the following problem:
After the user is authenticated, I don't seem to be able to retrieve the user's record from the back-end. The browser debugger does not even show a request being made. This is code:
// app/services/session-user.js
import Ember from 'ember';
const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend({
session: service('session'),
store: service(),
loadCurrentUser() {
currentUser: {
var userId = this.get('user_id');
if (!Ember.isEmpty(userId)) {
return this.get('store').findAll('user', userId);
}
}
}
});
There is a login controller which handles the authentication. But the code for getting the data is in the applications's route:
// app/routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
const { service } = Ember.inject;
export default Ember.Route.extend(ApplicationRouteMixin, {
sessionUser: service('session-user'),
beforeModel() {
if (this.session.isAuthenticated) {
return this._loadCurrentUser();
}
},
sessionAuthenticated() {
this._loadCurrentUser();
},
_loadCurrentUser() {
return this.get('sessionUser').loadCurrentUser();
},
});
For extra measure I am defining the session store:
// app/session-stores/application.js
import Adaptive from 'ember-simple-auth/session-stores/adaptive';
export default Adaptive.extend();
If there are files I should post, please let me know.
Any hints will be highly appreciated as I am rather new to ember. I have spent several hours researching without luck, as things seem to have changed quite a lot throughout versions.
Look at your service code.
var userId = this.get('user_id');
if (!Ember.isEmpty(userId)) {
return this.get('store').findAll('user', userId);
}
I don't see code that you provided in question where you setting up user_id variable. So if user_id not defined then if statement won't get executed because of !.

Resources