I am trying to build a Node.js server-side signup function for user authentication. The data for the user is being sent via "req.body" and the authentication database is provided by Appwrite.
The signup function should:
Create a user with the credentials provided in the request body.
Return the user details, such as the username and email.
Generate and return a token (cookie/JWT)
I am encountering issues with the Appwrite documentation and would appreciate guidance on building this function.
When trying to POST a new user using the Users API, an error of
createJWT is not a function
is produced, and when using the Account API, an error of
User (role: guests) missing scope (account)
is produced.
Here's the code I have:
const sdk = require('node-appwrite')
const client = sdk.Client()
client
.setEndpoint(endpoint)
.setProject(projectId)
.setKey('...')
const users = sdk.Users(client)
async function signup(req, res) {
try {
const { email, username } = req.body
let { password } = req.body
password = await bcrypt.hash(password, SALT_ROUNDS)
const result = await users.createBcryptUser("unique()", email, password, username)
// Create a token
// Combine data
res.send(userWithToken)
} catch (err) {
error('Failed to signup', err)
throw new Error(err)
}
}
The Users API is intended to be used in an admin perspective rather than as a user. You can use the Account API to execute things on behalf of a user, but the JWT token is typically generated client side and passed to the server, where you can call client.setJWT().
I am following the firebase documentation here to set custom auth claims for users logging into my app for the first time using firebase auth + identify platform but it does not seem to be working.
When a user logs in for the first time, I want them to get the admin custom claim. I have created the following blocking function and have verified from the logs that it runs when I log in for the first time to my app using sign-in with google:
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
return {
customClaims: {
admin: true,
},
};
});
I would expect this to create the admin custom claim in the user's token. However, when I get a list of claims using another cloud function the admin claim does not appear.
exports.getclaims = functions.https.onRequest(async (req, res) => {
const uid = req.query.uid as string;
if (uid) {
const user = await admin.auth().getUser(uid);
res.send(user.customClaims);
} else {
res.sendStatus(500);
}
});
If I set the claim using the admin SDK directly using the below cloud function, the admin claim does appear.
exports.setclaim = functions.https.onRequest(async (req, res) => {
const uid = req.query.uid as string;
if (uid) {
await admin.auth().setCustomUserClaims(uid, {admin: true});
res.sendStatus(200);
} else {
res.sendStatus(500);
}
});
What am I doing wrong in the beforeCreate function?
There's an open GitHub issue regarding that. See sessionClaims content not getting added to the decoded token. Also, there's a fix that has been recently merged regarding this issue.
From the snippet you provided, there does not appear to be anything wrong with beforeCreate as coded.
You may want to check you do not have a beforeSignIn that is overwriting the customClaims directly or via sessionClaims.
https://firebase.google.com/docs/auth/extend-with-blocking-functions#modifying_a_user
Try to use onCreate method instead of beforeCreate how it is shown on the official docs
functions.auth.user().onCreate(async (user) => {
try {
// Set custom user claims on this newly created user.
await getAuth().setCustomUserClaims(user.uid, {admin: true});
// Update real-time database to notify client to force refresh.
const metadataRef = getDatabase().ref('metadata/' + user.uid);
// Set the refresh time to the current UTC timestamp.
// This will be captured on the client to force a token refresh.
await metadataRef.set({refreshTime: new Date().getTime()});
} catch (error) {
console.log(error);
}
}
});
The main point here is that you need to create the user at first and then update claims and make the force update of the token at the client side:
firebase.auth().currentUser.getIdToken(true);
I am trying to send an email to reset-user-password. When I call the function I get a response back (link as string). But there is no email that was sent (just the link in the console). I have already made sure I was testing a valid email address, as a matter of fact in the Firebase Authentication console there is an option to manually send a reset password email and that does work. I would like to make it clear that I am getting a VALID response link back just no email (not even in spam folder).
Front-end call to firebase auth in backend
export const sendForgotPassword = async(email: string) => {
const task_forgotPassword = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/member/auth/forgotPassword`,
{params: {email}}
);
if (task_forgotPassword.data) return task_forgotPassword.data as string;
return null;
}
Backend Call to firebase auth from firebase library
export const forgotPassword = async(req:any, res:any) => {
const { email } = req.query;
return await admin
.auth()
.generatePasswordResetLink(email)
.then(async (_link) => {
console.log(_link, email, email.length);
res.send(`Sent reset password email to ${email}`);
})
.catch((err: FirebaseError) => {
res.send(err.message);
})
}
I am trying to write a REST microservice in Node.js that would deal with user authentication (among some other things) requests coming from different platforms.
What I would like is for the device to remember which user is signed in and keep the session for itself only. What currently happening is that I am able to login only one user at a time; if another user logs in from another device, the new user is returned as the currentUser. It's my first time using Firebase Authentication so I am very confused.
Here's the code for the login endpoint:
async signInUser( req, res, next ) {
var user = firebase.auth().currentUser;
if ( !user) {
var email = req.body.email;
var password = req.body.password;
// sign user in: if login fails, send error message as response
user = await firebase.auth().signInWithEmailAndPassword( email, password)
.catch( function( error) {
res.send( error.message);
});
}
// login successful: send user object as response
res.send( user);
}
Firebase have custom authentication you can pass your user unique id to firebase and genrate token for perticular user so you can able create unique session for different user and you can refer below link
https://firebase.google.com/docs/auth/admin/create-custom-tokens
I'm trying to impliment the code as found here: https://firebase.google.com/docs/auth/web/manage-users#send_a_password_reset_email
var auth = firebase.auth();
var emailAddress = "user#example.com";
auth.sendPasswordResetEmail(emailAddress).then(function() {
// Email sent.
}).catch(function(error) {
// An error happened.
});
But I can't find the sendPasswordResetEmail method in firebase admin.
Anyway I can do this on the backend?
ORIGINAL JULY 2018 ANSWER:
The sendPasswordResetEmail() method is a method from the client-side auth module, and you're right, the Admin-SDK doesn't have it - or anything comparable. Most folks call this function from the front-end...
With that said, it is possible to accomplish on the backend... but you'll have to create your own functionality. I've done this sort of thing before, I'll paste some of my code from my cloud functions to help you along... should you choose to go down this road. I create my own JWT, append it to a URL, and then use NodeMailer to send them an email with that link... when they visit that link (a password reset page) they enter their new password, and then when they click the submit button I pull the JWT out of the URL and pass it to my 2nd cloud function, which validates it and then resets their password.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
var jwt = require('jsonwebtoken');
admin.initializeApp()
// Email functionality
const nodemailer = require('nodemailer');
// Pull the gmail login info out of the environment variables
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
// Configure the nodemailer with our gmail info
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: gmailEmail,
pass: gmailPassword,
},
});
// Called from any login page, from the Forgot Password popup
// Accepts a user ID - finds that user in the database and gets the associated email
// Sends an email to that address containing a link to reset their password
exports.forgotPassword = functions.https.onRequest( (req, res) => {
// Make a query to the database to get the /userBasicInfo table...
admin.database().ref(`userBasicInfo`).once('value').then( dataSnapshot => {
let allUsers = dataSnapshot.val() ? dataSnapshot.val() : {};
let matchingUid = '';
let emailForUser = '';
// Loop over all of the users
Object.keys(allUsers).forEach( eachUid => {
// See if their email matches
allUsers[eachUid]['idFromSis'] = allUsers[eachUid]['idFromSis'] ? allUsers[eachUid]['idFromSis'] : '';
if (allUsers[eachUid]['idFromSis'].toUpperCase() === req.body.userIdToFind.toUpperCase()) {
// console.log(`Found matching user! Uid: ${eachUid} with idFromSis: ${allUsers[eachUid]['idFromSis']}... setting this as the matchingUid`);
matchingUid = eachUid;
emailForUser = allUsers[eachUid]['email'] ? allUsers[eachUid]['email'] : '';
}
})
// After loop, see if we found the matching user, and make sure they have an email address
if (matchingUid === '' || emailForUser == '') {
// Nothing found, send a failure response
res.send(false);
} else {
// Send an email to this email address containing the link to reset their password
// We need to generate a token for this user - expires in 1 hour = 60 minutes = 3600 seconds
jwt.sign({ uid: matchingUid }, functions.config().jwt.secret, { expiresIn: 60 * 60 }, (errorCreatingToken, tokenToSend) => {
if (errorCreatingToken) {
console.log('Error creating user token:');
console.log(errorCreatingToken);
let objToReplyWith = {
message: 'Error creating token for email. Please contact an adminstrator.'
}
res.json(objToReplyWith);
} else {
// Send token to user in email
// Initialize the mailOptions variable
const mailOptions = {
from: gmailEmail,
to: emailForUser,
};
// Building Email message.
mailOptions.subject = 'LMS Password Reset';
mailOptions.text = `
Dear ${req.body.userIdToFind.toUpperCase()},
The <system> at <company> has received a "Forgot Password" request for your account.
Please visit the following site to reset your password:
https://project.firebaseapp.com/home/reset-password-by-token/${tokenToSend}
If you have additional problems logging into LMS, please contact an adminstrator.
Sincerely,
<company>
`;
// Actually send the email, we need to reply with JSON
mailTransport.sendMail(mailOptions).then( () => {
// Successfully sent email
let objToReplyWith = {
message: 'An email has been sent to your email address containing a link to reset your password.'
}
res.json(objToReplyWith);
}).catch( err => {
// Failed to send email
console.log('There was an error while sending the email:');
console.log(err);
let objToReplyWith = {
message: 'Error sending password reset email. Please contact an adminstrator.'
}
res.json(objToReplyWith);
});
}
})
}
}).catch( err => {
console.log('Error finding all users in database:');
console.log(err);
res.send(false);
})
});
// Called when the unauthenticated user tries to reset their password from the reset-password-by-token page
// User received an email with a link to the reset-password-by-token/TOKEN-HERE page, with a valid token
// We need to validate that token, and if valid - reset the password
exports.forgotPasswordReset = functions.https.onRequest( (req, res) => {
// Look at the accessToken provided in the request, and have JWT verify whether it's valid or not
jwt.verify(req.body.accessToken, functions.config().jwt.secret, (errorDecodingToken, decodedToken) => {
if (errorDecodingToken) {
console.error('Error while verifying JWT token:');
console.log(error);
res.send(false);
}
// Token was valid, pull the UID out of the token for the user making this request
let requestorUid = decodedToken.uid;
admin.auth().updateUser(requestorUid, {
password: req.body.newPassword
}).then( userRecord => {
// Successfully updated password
let objToReplyWith = {
message: 'Successfully reset password'
}
res.json(objToReplyWith);
}).catch( error => {
console.log("Error updating password for user:");
console.log(error)
res.send(false);
});
});
});
JANUARY 2019 EDIT:
The Admin SDK now has some methods that allow you to generate a "password reset link" that will direct people to the built-in Firebase password reset page. This isn't exactly the solution OP was looking for, but it's close. You will still have to build and send the email, as my original answer shows, but you don't have to do everything else... i.e.: generate a JWT, build a page in your app to handle the JWT, and another backend function to actually reset the password.
Check out the docs on the email action links, specifically the "Generate password reset email link" section.
// Admin SDK API to generate the password reset link.
const email = 'user#example.com';
admin.auth().generatePasswordResetLink(email, actionCodeSettings)
.then((link) => {
// Do stuff with link here
// Construct password reset email template, embed the link and send
// using custom SMTP server
})
.catch((error) => {
// Some error occurred.
});
Full disclosure - I haven't actually used any of those functions, and I'm a little concerned that the page in question refers a lot to mobile apps - so you might have to pass it the mobile app config.
const actionCodeSettings = {
// URL you want to redirect back to. The domain (www.example.com) for
// this URL must be whitelisted in the Firebase Console.
url: 'https://www.example.com/checkout?cartId=1234',
// This must be true for email link sign-in.
handleCodeInApp: true,
iOS: {
bundleId: 'com.example.ios'
},
android: {
packageName: 'com.example.android',
installApp: true,
minimumVersion: '12'
},
// FDL custom domain.
dynamicLinkDomain: 'coolapp.page.link'
};
On the other hand, the page also says these features provide the ability to:
Ability to customize how the link is to be opened, through a mobile
app or a browser, and how to pass additional state information, etc.
Which sounds promising, allowing it to open in the browser... but if you are developing for web - and the function errors out when not provided iOS/Android information... then I'm afraid you'll have to do it the old fashioned approach and create your own implementation... but I'm leaning towards this .generatePasswordResetLink should work now.
Unlike other client features which work on the user object (e.g. sending a verification mail), sending a password reset email works on the auth module and doesn't require a logged-in user. Therefore, you can simply use the client library from the backend (provided you have the user's email address):
const firebase = require('firebase');
const test_email = "test#test.com";
const config = {} // TODO: fill
const app = firebase.initializeApp(config);
app.auth().sendPasswordResetEmail(test_email).then(() => {
console.log('email sent!');
}).catch(function(error) {
// An error happened.
});
public void resetpasswoord()
{
string emailaddress = resest_email.text;
FirebaseAuth.DefaultInstance.SendPasswordResetEmailAsync(emailaddress).ContinueWith((task =>
{
if (task.IsCompleted)
{
Debug.Log("Email sent.");
}
if (task.IsFaulted)
{
Firebase.FirebaseException e =
task.Exception.Flatten().InnerExceptions[0] as Firebase.FirebaseException;
GetErrorMessage((AuthError)e.ErrorCode);
errorpanal = true;
return;
}
}));
}
void GetErrorMessage(AuthError errorcode)
{
string msg = "";
msg = errorcode.ToString();
print(msg);
errorpanal = true;
ErrorText.text = msg;
}