NextAuth DB Query in Session Callback Not Working - javascript

I'm attempting to add data to a user's session from Firestore in a NextAuth callback, however, any form of DB query I seem to use, no matter the way I do it, will return a null session.
If I adjust the session data with a pre-defined value, everything works, but that's isn't what I would like to work.
This is my code:
callbacks: {
session: async (session, user) => {
db.collection("users").where("email", "==", session.user.email).get().then(query => {
query.docs.map(item => { session.session.user.myCustomData = item.data() })
})
// session.session.user.myCustomData = "Hello";
return session;
},
},
Thanks,

Related

I want to know how to got COUNT from SQL in reactjs

my request is good but i want to know how can i use my response in React.
SQL request :
```
exports.countAllComments = async (req, res) => {
const pId = req.params.id;
db.query(
"SELECT COUNT(*) FROM comments WHERE post_id = ?",
[pId],
(err, count) => {
if (err) {
res.status(500).json({ err });
console.log(err);
} else {
console.log(count)
res.status(200).json(count);
}
}
);
};
```
Front for fetch count:
```
const [countData, setCountData] = useState(0);
useEffect(() => {
const fetchCount = async () => {
try {
const fetchData = await Axios.get(
`http://localhost:3001/api/post/comment-count/${post.id}`,
{
headers: { Authorization: `Bearer ${test1.token}` },
}
);
setCountData(fetchData.data[0]);
} catch (err) {}
};
fetchCount();
}, [post.id, test1.token]);
console.log(countData);
```
console log return : "{COUNT(*): 4}" how can i get (4)
given your trivial example, the trivial solution would be something like -
fetchData.data[0]['COUNT(*)']
however, you should really have a think about the contract on the API, and enforce a certain return type from your API, and not just simply return the response from the SQL query. i.e. your API could possibly return an object like -
{ count: x }
where its up to your API to transform the result from the SQL query in a way that satisfies the contract, that way your React client is disconnected from your database layer and only cares about your API contract.
That way your client side becomes something like -
fetchData.data.count
which wouldn't break if the query where to be updated in some way etc.

Retrieve Authenticate User's Orders

GET method
Use accessToken of an authenticated user.
Only non-admin account can proceed.
User should be able to retrieve his orders only.
Router
router.get("/my-orders", auth.verify, (req, res) => {
const user = auth.decode(req.headers.authorization);
if (!user.isAdmin) {
UserController.getMyOrders(req.body).then(getMine => res.send(getMine));
} else {
return res.status(403).send("Access denied.");
}
});```
Controller
module.exports.getMyOrders = (body) => {
return User.find({}, {
"isAdmin": 0,
"_id": 0,
"password": 0
});
}
I am getting everything. Can someone help me code how to filter the user where the token belongs and retrieve his orders and not able to get other users' orders?
By passing an empty object in your .find method, you are telling mongodb to look for everything. I'm assuming in body you have some data to find a specific user, if so you would use that. eg. if body contains a username, you would write...
module.exports.getMyOrders = (body) => {
return User.find({username: body.username});
}
Here is some more info on db.collection.find()
EDIT - Look up user by JWT:
router.get("/my-orders", auth.verify, (req, res) => {
//Here you have decoded your JWT and saved it as user
const user = auth.decode(req.headers.authorization);
if (!user.isAdmin) {
//here you are passing user instead of req.body
UserController.getMyOrders(user).then(getMine => res.send(getMine));
} else {
return res.status(403).send("Access denied.");
}
});
module.exports.getMyOrders = (user) => {
//now you are using 'username' from the decoded jwt to look up the user
return User.find({username: user.username});
}

Authenticated requests after sign in with React Query and NextAuth

I'm having troubled sending an authenticated request to my API immediately after signing in to my Nextjs app using NextAuth. The request that is sent after signing in returns data for and unauthenticated user.
I believe the issue is that React Query is using a previous version of the query function with an undefined jwt (which means its unauthenticated). It makes sense because the query key is not changing so React Query does not think it's a new query, but, I was under the impression that signing in would cause loading to be set to true temporarily then back to false, which would cause React Query to send a fresh request.
I've tried invalidating all the queries in the app using queryClient, but that did not work. I've also used React Query Devtools to invalidate this specific query after signing in but it still returns the unauthenticated request. Only after refreshing the page does it actually send the authenticated request.
// useGetHome.js
const useGetHome = () => {
const [session, loading] = useSession();
console.log(`session?.jwt: ${session?.jwt}`);
return useQuery(
'home',
() => fetcher(`/home`, session?.jwt),
{
enabled: !loading,
},
);
}
// fetcher
const fetcher = (url, token) => {
console.log(`token: ${token}`);
let opts = {};
if (token) {
opts = {
headers: {
Authorization: `Bearer ${token}`,
},
};
}
const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}${url}`, opts);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message);
}
return res.json();
}
// Home.js
const Home = () => {
const { data: home_data, isLoading, error } = useGetHome();
...
return(
...
)
}
Attached is the console immediately after signing in. You can see the the session object contains the jwt after signing in, but in the fetcher function it is undefined.
console after signing in
Any help here is appreciated. Is there a better way to handle authenticated requests using React Query and NextAuth? Thank you!
I have tried a similar situation here and struggled the same thing but the enabled property worked fine for me and it is good to go right now.
https://github.com/maxtsh/music
Just check my repo to see how it works, that might help.

Where and how to change session user object after signing in?

I have weird problem and I don't know where the problem is. I use next-auth library to make authentication system in my Next.js app.
Everything is OK - I can sign in by checking if there is account in google firebase with submitted credentials, session is being created properly, but when the authorize callback is initialized I pass data received from google firestore after correct sign in. User object contains whole data and it's passed forward.
Then, when I want to read data from session, some data I passed before is missing.
Code:
/pages/api/auth/[...nextauth.js]
export default (req, res) =>
NextAuth(req, res, {
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
phone: { label: "Phone number", type: "text" },
password: { label: "Password", type: "password" }
},
authorize: async (loginData) => {
const { csrfToken, phone, password } = loginData;
// checking if there is account with these credentials
let res = await login({
phone,
password: sha1(md5(password)).toString()
})
// 200 = OK
if(res.status == 200){
// collect account data
const user = {
phone,
...res.data.info
}
// user object is created correctly, whole data is stored there
console.log('received account data from firestore', user)
return Promise.resolve(user);
}
else {
// wrong credentials
return Promise.resolve(null);
}
}
})
],
callbacks: {
session: async (session, user) => {
console.log('data passed to object when signed in', user)
// user object there doesn't have all data passed before
return Promise.resolve(session)
}
},
debug: false
})
Console logged objects:
received account data from firestore
{
phone: '123123123',
id: 'w2zh88BZzSv5BJeXZeZX',
email: 'jan#gmail.com',
name: 'Jan',
surname: 'Kowalski'
}
data passed to object when signed in
{
name: 'Jan',
email: 'jan#gmail.com',
iat: 1603900133,
exp: 1606492133
}
The best thing is, the object (above) always has the same properties. I can pass any object in authorize callback, but in session, user object always has "name, email, iat, exp" ALWAYS. The only thing that changes are values of these two properties in object (name, email). (rest properties - "phone, id, surname" - are missing).
Below there is console logged session object in any react component:
import {
signIn,
signOut,
useSession
} from 'next-auth/client'
const [ session, loading ] = useSession();
console.log(session)
Photo of console logged session object
What can I do? Do I have to receive data from firestore separately in session callback? Is the server-side rendering of Next.js causing the problem?
I resolved that problem by myself.
This issue thread helped me a lot!
https://github.com/nextauthjs/next-auth/issues/764
Below is the explanation:
callbacks: {
jwt: async (token, user, account, profile, isNewUser) => {
// "user" parameter is the object received from "authorize"
// "token" is being send below to "session" callback...
// ...so we set "user" param of "token" to object from "authorize"...
// ...and return it...
user && (token.user = user);
return Promise.resolve(token) // ...here
},
session: async (session, user, sessionToken) => {
// "session" is current session object
// below we set "user" param of "session" to value received from "jwt" callback
session.user = user.user;
return Promise.resolve(session)
}
}
EDIT: Due to NextAuth update to v4
Version 4 of NextAuth brings some changes to callbacks shown above. Now there is only one argument assigned to jwt and session functions. However, you can destructure it to separate variables. Rest of the code stays the same as before.
https://next-auth.js.org/configuration/callbacks#jwt-callback
https://next-auth.js.org/configuration/callbacks#session-callback
// api/auth/[...nextauth].js
...
callbacks: {
jwt: async ({ token, user }) => {
user && (token.user = user)
return token
},
session: async ({ session, token }) => {
session.user = token.user
return session
}
}
...
I had this problem too, and it turned out to be the functions in the Adapter that caused the problem. In my case, I wanted to use v4 of next-auth, and I wanted to use DynamoDB. As there is no official v4 adapter for Dynamo, I had to write my own, basing it on the publicly available v3 adapter.
One of the functions you have to provide when creating your own adapter is:
async updateUser(user) {
// use ddb client to update the user record
// client returns `data`
return { ...user, ...data.Attributes }
}
It turns out that the data in data.Attributes is what gets passed to your jwt() callback as user. This seems to be different to the v3 implementation.
Therefore in my case I had to structure the dynamodb adapter's updateUser() function to instruct the client to return ALL_NEW and not merely UPDATED_NEW (which is what the v3 adapter does).
My complete updateUser() function is as follows - bear in mind that at this point, I used the recommended dynamodb table structure (which I don't necessarily agree with, especially in my use case, but that's another story)
async updateUser(user) {
const now = new Date()
const data = await client.update({
TableName: tableName,
Key: {
pk: `USER#${user.id}`,
sk: `USER#${user.id}`,
},
UpdateExpression:
"set #emailVerified = :emailVerified, #updatedAt = :updatedAt",
ExpressionAttributeNames: {
"#emailVerified": "emailVerified",
"#updatedAt": "updatedAt",
},
ExpressionAttributeValues: {
":emailVerified": user.emailVerified?.toISOString() ?? null,
":updatedAt": now.toISOString(),
},
ReturnValues: "ALL_NEW",
}).promise()
return { ...user, ...data.Attributes }
},
As a result, I see user fully populated in the jwt() callback:
callbacks: {
async jwt({ token, user, account, profile, isNewUser }) {
console.debug("callback jwt user:", user)
user && (token.user = user)
return token
},
The debug output is the complete record for the user from DynamoDB, and can be used as described in #MateuszWawrzynski's answer.

Storing user in database within Firestore

I am working with Firebase and I'm having some troubles. When creating a new user, I am able to store it in my database, but later, when accessing to another component it fails.
//Register a new user in our system
registerUserByEmail(email: string, pass: string) {
return this.afAuth.auth
.createUserWithEmailAndPassword(email, pass)
.then(res => {
this.email = res.user.email;
let user = {
email: this.email,
goal: "",
previousGoals: [],
progress: {
accomplishedToday: false,
completedGoals: 0,
daysInRow: 0,
unlockedBadges: []
}
};
// store in database
new Promise<any>((resolve, reject) => {
this.firestore
.collection("users")
.add(user)
.then(
res => {
console.log(res.id);
this.isAuthenticated = true;
this.router.navigate(["/dashboard"]);
},
err => reject(err)
);
});
});
}
I believe that this piece of code is basically registering as a user the email and storing it successfully into my database (checked it).
Nevertheless, when rendering home.component or /dashboard
home.component
ngOnInit() {
this.setAuthStatusListener();
this.getUser();
}
getUser() {
this.data.getUser().subscribe(user => {
this.user = user.payload.data();
});
}
data.service
getUser() {
return this.firestore
.collection("users")
.doc(this.currentUser.uid)
.snapshotChanges();
}
I get the following error
ERROR
TypeError: Cannot read property 'uid' of null
It looks like by the time you call getUser the user hasn't been authenticated yet.
The simple fix to get rid of the error is to check for this condition in your DataService's getUser:
getUser() {
if (this.currentUser) {
return this.firestore
.collection("users")
.doc(this.currentUser.uid)
.snapshotChanges();
}
}
Given this sequence of calls however, I think there may be a better way to handle your use-case:
ngOnInit() {
this.setAuthStatusListener();
this.getUser();
}
Since you're attaching an auth state listener, you probably want to only start watching the user's data in Firestore once the user has actually been authenticated.
Once simple way to do that is to call out to your DataService's getUser() method from within the auth state listener, once the user is authenticated. Something like this:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.getUser(); // call the DataService's getUser method
}
});

Categories

Resources