Next Auth getSession not working in api routes - reactjs

So basically I use getServerSideProps to call some APIs. when I call getSession in getServerSideProps() I get a valid object.
export async function getServerSideProps({ req }) {
const session = await getSession({ req }); // works
But when I call it in the API that is called in that getServerSideProps() function, I get null.
import { getSession } from "next-auth/react";
export default async (req, res) => {
const { db } = await connectToDatabase();
const session = await getSession({ req }); // returns null
Here is NextAuth documentation for reference:

This is very late, but I found the section in the docs where you can get the appropriate session object in API in this section.
Using unstable_getServerSession()
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
export default async (req, res) => {
const session = await unstable_getServerSession(req, res, authOptions)
if (session) {
// Signed in
console.log("Session", JSON.stringify(session, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
}
Using getToken()
// This is an example of how to read a JSON Web Token from an API route
import { getToken } from "next-auth/jwt"
export default async (req, res) => {
// If you don't have NEXTAUTH_SECRET set, you will have to pass your secret as `secret` to `getToken`
const token = await getToken({ req })
if (token) {
// Signed in
console.log("JSON Web Token", JSON.stringify(token, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
}
The most important part is to pass the authOptions that is imported from /api/[...nextauth]
NOTE: getSession is a client API, as in it will only work on getStaticProps

Related

Intercepting Auth0 getSession with MSW.js and Cypress

I'm building NextJS app with SSR. I've written the getServerSideProps function that makes a call to supabase. Before making the call I'm trying to get user session by calling getSession function from #auth0/nextjs-auth0 package.
I'm trying to mock it in the handlers.ts file:
import { rest } from 'msw';
export const handlers = [
// this is the endpoint called by getSession
rest.get('/api/auth/session', (_req, res, ctx) => {
return res(ctx.json(USER_DATA));
}),
rest.get('https://<supabase-id>.supabase.co/rest/v1/something', (_req, res, ctx) => {
return res(ctx.json(SOMETHING));
}),
];
My mocks file: requestMocks/index.ts:
export const initMockServer = async () => {
const { server } = await import('./server');
server.listen();
return server;
};
export const initMockBrowser = async () => {
const { worker } = await import('./browser');
worker.start();
return worker;
};
export const initMocks = async () => {
if (typeof window === 'undefined') {
console.log('<<<< setup server');
return initMockServer();
}
console.log('<<<< setup browser');
return initMockBrowser();
};
initMocks();
Finally, I'm calling it in the _app.tsx file:
if (process.env.NEXT_PUBLIC_API_MOCKING === 'true') {
require('../requestMocks');
}
Unfortunately, it does work for me. I'm getting no user session data in the getServerSideProps function in my page component:
import { getSession } from '#auth0/nextjs-auth0';
export const getServerSideProps = async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) => {
const session = getSession(req, res);
if (!session?.user.accessToken) {
// I'm constantly falling here
console.log('no.session');
return { props: { something: [] } };
}
// DO something else
};
Any suggestions on how to make it working in Cypress tests would be great.
I'm expecting that I will be able to mock requests made in getServerSideProps function with MSW.js library.
I made it finally. Looks like I don't have to mock any calls. I need to copy my user appSession cookie and save it in cypress/fixtures/appSessionCookie.json file:
{
"appSession": "<cookie-value>"
}
Then use it in tests as follows:
before(() => {
cy.fixture('appSessionCookie').then((cookie) => {
cy.setCookie('appSession', cookie.appSession);
});
});
This makes a user automatically logged in with Auth0.

How to wait for a certain axios response before triggering redux-toolkit middleware? - react, socket.io, redux toolkit & express

tl;dr: I want to wait for the axios request response in App.tsx before the const socket = io() initialization in socketMiddleware.ts triggers
The authorization headers are received through an axios request as soon as the client loads the react app.
This axios request triggers as soon as the react app is loaded and refreshes after a certain time.
In App.tsx:
App.tsx
const silentRefresh = useCallback(async () => {
try {
const response: AxiosResponse = await axios.get(
(import.meta.env.VITE_BASEURL as string) + 'auth/refresh-token'
)
axios.defaults.headers.common[
'Authorization'
] = `Bearer ${response.data.token}`
//set user
setTimeout(() => {
silentRefresh()
}, response.data.expiresIn * 1000 - 10000)
} catch (response: any) {
if (response.status !== 201) {
console.log('Not Authorized')
}
}
}, [dispatch])
useEffect(() => {
silentRefresh()
}, [silentRefresh])
This sets the authorization headers (if the client has the httpOnly cookie, to automatically log in) which authorizes the user for protected API endpoints of my express server, and refresh after a certain time.
I want to use this header as authorization token for the socket connection too.
In my redux-toolkit store.ts I added a middleware:
store.ts
const store = configureStore({
reducer: {/*reducers...*/},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(socketMiddleware()),
})
The socketMiddleware.ts looks like this, it tries to set the token from the headers but they are not received at this point:
socketMiddleware.ts
const socketMiddleware = () => {
const socket = io(import.meta.env.VITE_BASEURL as string, {
withCredentials: true,
auth: {
token: axios.defaults.headers.common['Authorization'],
},
})
return (store: any) => (next: any) => (action: any) => {
//some code...
next(action)
}
}
On my server.ts I check for the auth token, but it is undefined as the socket connection gets established before the auth headers are set on the client through the axios request in App.tsx
server.ts
io.use((socket, next) => {
try {
const token = socket.handshake.auth.token
if (!token) {
const error: any = new Error('No token sent, authorization denied')
error.statusCode = 401
next(error)
}
const decoded: any = jwt.verify(token, process.env.REFRESH_SECRET as string)
next()
} catch (error) {
console.log(error)
}
})
io.on('connection', (socket) => {
socket.emit('Hello from Server')
})
Thank you for your help.

how to use an Axios interceptor with Next-Auth

I am converting my CRA app to Nextjs and running into some issues with my Axios interceptor pattern.
It works, but I am forced to create and pass an Axios instance to every api call.
Is there a better way to do this?
Here is what I have now:
Profile.js:
import { useSession } from 'next-auth/react'
function Profile(props) {
const { data: session } = useSession()
const [user, setUser] = useState()
useEffect(()=> {
const proc= async ()=> {
const user = await getUser(session?.user?.userId)
setUser(user)
}
proc()
},[])
return <div> Hello {user.userName}<div>
}
getUser.js:
export default async function getUser(userId) {
const axiosInstance = useAxios()
const url = apiBase + `/user/${userId}`
const { data } = await axiosInstance.get(url)
return data
}
useAxios.js:
import axios from 'axios'
import { useSession } from 'next-auth/react'
const getInstance = (token) => {
const axiosApiInstance = axios.create()
axiosApiInstance.interceptors.request.use(
(config) => {
if (token && !config.url.includes('authenticate')) {
config.headers.common = {
Authorization: `${token}`
}
}
return config
},
(error) => {
Promise.reject(error)
}
)
return axiosApiInstance
}
export default function useAxios() {
const session = useSession()
const token = session?.data?.token?.accessToken
return getInstance(token)
}
In case anyone else has this problem, this was how i solved it (using getSession):
credit to:
https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1993281
import axios from 'axios'
import { getSession } from 'next-auth/react'
const ApiClient = () => {
const instance = axios.create()
instance.interceptors.request.use(async (request) => {
const session = await getSession()
if (session) {
request.headers.common = {
Authorization: `${session.token.accessToken}`
}
}
return request
})
instance.interceptors.response.use(
(response) => {
return response
},
(error) => {
console.log(`error`, error)
}
)
return instance
}
export default ApiClient()
There is actually a neat way on including user extended details to session object
// /api/[...nextauth].ts
...
callbacks: {
session({ session, user, token }) {
// fetch user profile here. you could utilize contents of token and user
const profile = getUser(user.userId)
// once done above, you can now attach profile to session object
session.profile = profile;
return session;
}
},
The you could utilize it as:
const { data: session } = useSession()
// Should display profile details not included in session.user
console.log(session.profile)
I know one way to do this is to use
const session = await getSession()
Is there any other way to go about it without using await getSession() because what this does is that it makes a network request to get your session every time your Axios request runs?

Directly import next.js API endpoint in getServerSideProps()

When fetching data using getServerSideProps() in Next.js, they recommend directly importing the API endpoint instead of using fetch() and running another HTTP request. This makes sense, and I was able to get it working until implemented middleware for my API (note, I'm using the API feature built into Next.js). Now with middleware implemented, I can't export functions that use the middleware, I have to export the handler. See below:
const handler = nextConnect();
handler.use(middleware);
handler.get(async (req, res) => {
const post = await req.db.collection("posts").findOne();
res.send({
post: post,
});
});
export default handler;
What would be the recommend way to import my API endpoint into getServerSideProps? I would like to do something as follows, but the getPost() function no longer has access to the database middleware:
export const getPost = async () => {
const post = await req.db.collection("posts").findOne();
return post;
}
handler.get(async (req, res) => {
res.send({
post: getPost(),
});
});
and then in my next.js page:
import { getPost } from './api/post';
...
export async function getServerSideProps(context) {
return {
props: {
post: getPost(),
}
}
}
In any case, you'll have to pass the req and res objects to the function. But if you do the following, the post prop should be populated with a NextApiResponse instance, which at it's base is a stream.Writable object, which is probably not what you want...
import { getPost } from './api/post';
...
export async function getServerSideProps({req, res}) {
return {
props: {
post: await getPost(req, res),
}
}
}
You could try to read the stream, but that seems like more trouble than refactoring your code, but if you call getPost(req, res).end(), I think you should get the streamed data, but I'm not sure how it will be formatted. You'd have to check.
You could split your functions up a little more..
// In your api endpoint:
const handler = nextConnect();
handler.use(middleware);
export async function getPostMethod(db) {
return await db.collection("posts").findOne();
}
handler.get(async (req, res) => {
res.send({
post: await getPostMethod(req, res, req.db)
})
});
export default handler;
// In your page component:
export async function getServerSideProps({req, res}) {
// Do what ever you have to do here to get your database connection
const db = await whereIsMyDb()
return {
props: {
post: await getPostMethod(db),
}
}
}

Using Auth0 React Hook on all Axios requests

I have set up Auth0 with React by following their Quickstart tutorial.
Basically my React app is wrapped around their Context Provider and I have access to the useAuth0 hook in any of my components.
This is how I would make a request to my API:
const TestComponent = () => {
const { getTokenSilently } = useAuth0();
const getObjectsFromAPI = async () => {
const token = await getTokenSilently();
const axiosConfig = {
headers: {
Authorization: "Bearer " + token
}
};
const response = await axios.get(
"/api/objects/",
axiosConfig
);
// ... do something with the response
};
return ... removed code for brevity
};
Is there a way to make the requests without having to write the token and axiosConfig on each request?
I know that I can initialize a new axios instance with a config, but I cannot use the useAuth0 hook outside the Context Provider.
but I cannot use the useAuth0 hook outside the Context Provider.
Right, not sure how you can avoid token generation per request but you can save the axios config part by passing the token to a shared axios instance, something like:
http.js
const instance = axios.create({
// your config
});
export const authorized = (token) => {
instance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
return instance;
}
And in your component:
import http from '/path/to/above/http.js';
const TestComponent = () => {
const { getTokenSilently } = useAuth0();
const getObjectsFromAPI = async () => {
const token = await getTokenSilently();
const response = await http
.authorized(token)
.get('/api/objects/');
// ...
};
};

Resources