I m running a Wordpress site and I have set up Auth0 passwordless log in using the Lock Passwordless UI.
The passwordless authentication does not allow additional fields for the sign up and I successfully wrote a rule (not sure if is OK but it works) that redirects the user that logs in for the first time to a sign up form on Wordpress asking for more details from them.
function (user, context, callback) {
const namespace = 'https://mysiteurl/redirect';
const userType = context.stats && context.stats.loginsCount === 1 ? 'new' : 'existing';
context.idToken[`${namespace}userType`] = userType;
if (userType === 'new') {
context.redirect = {
url: "https://mys-site-url/sign-up-wordpress-form"
};
}
return callback(null, user, context);
}
What I would like to have is a permanent registration form on Wordpress where I collect data and the sign in process to be passwordless with email only.
Related
I am building a blog platform using Nextjs 12, NextAuth(Google), Prisma(MySQL). When a user first signs up to my platform, NextAuth automatically saves user's google email address and google name to my database. I want to make user change their nickname when first signing up.
How would I know if the user is signing up or signing in? Currently in NextAuth, you click signup() button and you are good for both signup and signin..
I would extend the base user model with a property such as customName, and then check on the front- or back-end whether the customName property is undefined/empty, and redirect or show a modal accordinglty.
You can perform the front-end check via useSession
const { data: session, status } = useSession()
if(status === "authenticated" && session.user.customName === ""){ // or whatever your default value for non-defined fields is
// Show your modal or redirect to the page where the user can change his username
// after user enters his new name, make an API call and update it in your DB
}
or on the back-end in pages/api/auth/[...nextauth].js:
...
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
if (user.customName) {
return true
} else {
// User has no custom name yet, redirect him
return '/pathWhereUserCanSetHisName'
}
}
}
...
If you want to ensure every user has to set a name before continuing, I would put the logic above into an API middleware or next's edge middleware like this:
For example, if you're using NextAuth JWT sessions via cookies, add a custom cookie name for the sessionToken and parse it within the next.js middleware:
middleware.ts (or .js)
export default async function middleware(request: NextRequest) {
const response = NextResponse.next();
const userCookie = request.cookies.get(YOUR_CUSTOM_COOKIE_NAME) // the cookie name you set for NextAuth's sessionToken
if(!userCookie){
// user not logged in
return NextResponse.redirect('/login')
}
const user = yourCustomUserParsingFunction(userCookie) // parsing the JWT contained in the cookie
if(user.customName){
return response; // user has a custom name, don't intervene
}
// user has no customName, intervene
return NextResponse.redirect('/pathWhereUserCanSetHisName');
}
I am currently implementing a MFA system with Firebase Authentication & Google Authenticator.
Since my users are not allowed to authenticate with a non-verified email address, I'd like to prevent them from signing-in if their Firebase Authentication email_verified is set to false. To do that, I am using Google Cloud Identity Provider blocking functions, this works perfectly.
However, when it comes to the registration beforeCreate blocking function hook, I can't find a way to generate an email verification link for the user currently being created, the documentation says:
Requiring email verification on registration The following example
shows how to require a user to verify their email after registering:
export.beforeCreate = authClient.functions().beforeCreateHandler((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth()
.generateEmailVerificationLink(user.email)
.then((link) => {
return sendCustomVerificationEmail(
user.email, link, locale
);
});
}
});
export.beforeSignIn = authClient.functions().beforeSignInHandler((user, context) => {
if (user.email && !user.emailVerified) {
throw new gcipCloudFunctions.https.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
However, as far as I understand, generateEmailVerificationLink() can only be called to generate email verification link of an existing Firebase Authentication user. At this stage (while running beforeCreate blocking function), the user is not created yet.
Now I am wondering, I am missing something or is the Google documentation wrong?
No.
User data is created upon registration in the database.
Then, you may send an Email-Verification with a link automatically.
This Email-Verification just updates the field emaiVerified of said user data.
If you want to prevent users with unverified Emails from logging in, you need to adjust your Login page and check whether emaiVerified is true.
Important: Google will sign in a user right upon registration whether the email is verified or not, as this is the expected behavior from the perspective of a user. Email verification is ensured on the second, manual login.
(Also, please do not screenshot code.)
You can let a user sign in via email link at first, and call firebase.User.updatePassword() to set its password.
I am using Angular-Firebase, this is the logic code.
if (this.fireAuth.isSignInWithEmailLink(this.router.url)) {
const email = this.storage.get(SIGN_IN_EMAIL_KEY) as string;
this.storage.delete(SIGN_IN_EMAIL_KEY);
this.emailVerified = true;
this.accountCtrl.setValue(email);
from(this.fireAuth.signInWithEmailLink(email, this.router.url)).pipe(
catchError((error: FirebaseError) => {
const notification = this.notification;
notification.openError(notification.stripMessage(error.message));
this.emailVerified = false;
return of(null);
}),
filter((result) => !!result)
).subscribe((credential) => {
this.user = credential.user;
});
}
const notification = this.notification;
const info = form.value;
this.requesting = true;
form.control.disable();
(this.emailVerified ? from(this.user.updatePassword(info.password)) : from(this.fireAuth.signInWithEmailLink(info.account))).pipe(
catchError((error: FirebaseError) => {
switch (error.code) {
case AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY.POPUP_CLOSED_BY_USER:
break;
default:
console.log(error.code);
notification.openError(notification.stripMessage(error.message));
}
this.requesting = false;
form.control.enable();
return of(null);
}),
filter((result) => !!result)
).subscribe((result: firebase.auth.UserCredential) => {
if (this.emailVerified) {
if (result.user) {
notification.openError(`注册成功。`);
this.router.navigateByUrl(this.authService.redirectUrl || '');
} else {
notification.openError(`注册失败。`);
this.requesting = false;
form.control.enable();
}
} else {
this.storage.set(SIGN_IN_EMAIL_KEY, info.account);
}
});
Mate, if database won't create a new user using his email and password, and you send him email verification which will create his account, how the heck database will know his password? If it didn't create his account in the first step? Stop overthinking and just secure database using rules and routes in application if you don't want user to read some data while he didn't confirm email address.
It is that simple:
match /secretCollection/{docId} {
allow read, write: if isEmailVerified()
}
function isEmailVerified() {
return request.auth.token.email_verified
}
I think the blocking function documentation is wrong.
beforeCreate: "Triggers before a new user is saved to the Firebase Authentication database, and before a token is returned to your client app."
generateEmailVerificationLink: "To generate an email verification link, provide the existing user’s unverified email... The operation will resolve with the email action link. The email used must belong to an existing user."
Has anyone come up with a work around while still using blocking functions?
Using firebase rules to check for verification isn't helpful if the goal is to perform some action in the blocking function, such as setting custom claims.
I have 2 apps, one for auth while the other for products. After login or verification of email, the user is redirected to the products app. On redirection, the recently logged in user is instantly logged out, so a reference to the logged in user becomes null, and I'll need the logged in credentials in the auth app to authenticate on the second. How do I maintain the logged in state in the auth app when it has redirected to the products app?
This is the login function on the auth app
var callLogin = function (email, password, router) {
Meteor.loginWithPassword(email, password, ( error )=> {
if (error) {
sAlert.error( error );
} else {
sAlert.success("Logged in successfully");
window.location.replace( "http://localhost:3300/" + Meteor.userId() );
}
});
}
This is the onCreated function on the products app
Tracker.autorun(function () {
let router = FlowRouter.getParam("_id");
let AuthConnection = DDP.connect( AuthURL );
if ( AuthConnection ) {
console.log( router );
AuthConnection.call('logins.user', router, ( error, response )=> {
if ( error ) {
console.log( error );
} console.log( response );
} );
}
});
The logged in user is always present until the redirection that it becomes null. What do I do to maintain the logged in state of the user in the auth app?
I assume both apps connect to the same database?
When you redirect your local state changes - specifically in this case your local storage state which tracks user resume tokens.
If you want to allow one app to authenticate for another you need some form of SSO - a trivial implementation would be after login to request a resumeToken from the server, pass that to your second app in the URL, then use Meteor.loginWithToken. A more hacky (but maybe simpler?) way might be to copy the token saved in localStorage from app1, pass it in the URL to app2 then use save it there too.
I'm using Meteor User accounts with multiples services (google, facebook, ...)
I don't understand why when I'm registering with google and facebook with the same email address, mongodb creating 2 different accounts.
I'm using the default Meteor User accounts settings.
(I have no code to show you)
Meteor doesn't merge user accounts from different services based on email address. If the user's login credentials aren't present in the database yet, a new user gets created. Automatically merging Facebook, Google, and email/password credentials is a potential security hole.
However, I believe it's possible to merge them manually. Login credentials are stored in db.users.services document, and it should be possible to have more than one login method per user. I wouldn't recommend that though.
If you want to ensure the same email isn't used twice, you can do this:
Accounts.onCreateUser(function(options, user) {
var service, serviceName;
if (!user.profile) {
user.profile = options.profile || {};
}
if(user.services) {
// Get first service
serviceName = _.keys(user.services)[0];
user.meta.service = serviceName;
if (!user.emails || !user.emails.length) {
if(serviceName === "facebook" || serviceName === "google") {
service = user.services[serviceName];
user.emails = user.emails || [];
user.emails[0] = {
address: service.email,
verified: service.verified_email
};
}
}
}
return user;
});
This will add the email field from the provider in the users "email" field, and it will prevent users from registering twice with two different accounts.
I am trying to build a basic web application w/ user authentication via email/password registration using Firebase.
My setup right now includes a main.js file that consists of the following:
var dbRef = new Firebase('https://url.firebaseIO.com');
var authClient = new FirebaseAuthClient(dbRef, function(error, user) {
if (error) {
// an error occurred while attempting login
console.log(error);
} else if (user) {
// user authenticated with Firebase
console.log('User ID: ' + user.id + ', Provider: ' + user.provider);
} else {
// user is logged out
console.log('logged out!');
}
});
function next(){
window.location = 'index.html';
}
function test(){
authClient.login('password', {
email: email,
password: password,
rememberMe: true
},next());
// window.location = 'index.html';
}
I obtain email/password values from a form and login. That works. But as soon as I include a callback function to then redirect them to a new authenticated page, it no longer works. In fact, most of the time I get an "UNKOWN ERROR" response.
When I get to the next page, I am no longer logged in. If I remove the next() function and stay on the same page, it works - even if I then trigger the next function from the console. Is there a different way you are supposed to proceed to another page?
I'm pretty sure there is some sort of communication issue (possibly the login does not get a return before the page is switched?) because if I add a 1s timeout before the next function, it then works. But surely this is not best practice?
Thanks!
Per https://www.firebase.com/docs/security/simple-login-email-password.html, the authClient.login() method does not actually accept a callback, so the problem you're seeing is likely the result of navigating away from the current page before the callback is returned, as you suggested.
I would recommend doing the redirect in the callback you're passing during the instantiation of the auth client. (new FirebaseAuthClient(ref, callback)) and redirect if you detect a logged-in user. This callback will be invoked once upon instantiation with the current authentication state of the user, and then again any time the user's authentication state changes (such as on login or logout).