In my meteor app, I'm setting up the registration process.
Meteor has a Account.sendVerificationEmail method to send emails out to new users with a token to verify their email address
My app does need this functionality but I don't really want to use the sendVerificationEmail to send the emails because I already have my own email helper which has got a bunch of logic and I want all the emails in my system to pass to flow out of that function.
So my question is that I do want to create my verification token for the user on registration, but I don't want sendVerificationEmail to send an email out because I want to do it manually.
Is this possible?
First add the core "random" package for random code generation
$ meteor add random
Then intercept the account creation process
Accounts.onCreateUser(function(options, user) {
// create a verified flag and set it false
user.customVerified = false;
//20 character random lowercase hex string. You can use a hash of some user info if you like. I just put this here for demonstration of the concept :)
user.customVerificationCode = Random.hexString(20).toLowerCase();
//pass the new user's email and the verification code to your custom email function so that you can craft and send the mail. Please double check the option.profile.emails[0], the email should be available somewhere within the options object
myCustomEmailFunction(options.profile.emails[0], user.customVerificationCode);
// continue with account creation
return user;
});
At this point, if you don't want to show pieces of ui elements to unverified users, you can create a template helper for that. Or you can check if user is verified in your publications. Etc... whatever you want to restrict.
Now you can define a route in your app with iron router so that when the user clicks on the link, the route takes the verification code and set's the user's verified flag to true.
You can fake the verification record for the user in the MongoDB document and then send your own email:
//Fake the verificationToken by creating our own token
var token = Random.secret();
var tokenRecord = {
token: token,
address: Meteor.user().emails[0].address,
when: new Date(),
};
//Save the user
Meteor.users.update(
{_id: Meteor.userId()},
{$push: {'services.email.verificationTokens': tokenRecord}}
, function(err){
//Send an email containing this URL
var confirmUrl = Meteor.absoluteUrl() + '#/verify-email/' + token;
//Send using SendGrid, Mandrill, MailGun etc
});
Code taken from GitHub:
https://github.com/meteor/meteor/blob/5931bcdae362e1026ceb8a08e5a4b053ce5340b7/packages/accounts-password/password_server.js
Related
I would like to implement this authentication flow in Keycloak:
A user creates an account by typing only his email
The user is logged in and can access my service
2'. At the same time, an email is sent to him, allowing him to "finalize" his account
The user leaves his session -> to reuse my service, he must click in the received email
By clicking in the received email, the user defines his first password
The user is then logged in automatically (without going through a login page).
The objective of this flow is to be the simplest, to hook users who are not used to webapps.
The implementation I would do:
Create an account without password request: I customize the Keycloak Registration flow by disabling the Password Validation and Profile Validation rules
Programmatically, in my webapp, at the first connection of a user, via the REST Admin API, I trigger the email action UPDATE_PASSWORD
I get something that works, but:
A. The link received by email redirects to an intermediary page confirming the execution of actions ("Perform the following action (s)") - (similar to Keycloak Implement Reset password flow same as forgot password flow)
B. The user is then redirected to a login page, and not directly connected to the application.
When, as a normal user, I trigger a reset password request (through 'forget password' feature), the process is the one I want: by clicking on the email link, I go directly to the page allowing me to enter and confirm a new password, then I'm authenticated.
My question: Do you see a way to implement this 'simplified' flow?
My keycloak version : 11.0.2
Thank you !
I could remove the "info.ftl" page display, customizing the "ExecuteActionsActionTokenHandler", as explained here :
action-token-spi
You have to create a file :
src/main/resources/META-INF/services/org.keycloak.authentication.actiontoken.ActionTokenHandlerFactory
containing the name of the class you want to use instead :
com.example.ExecuteActionTokenHandlerFactory
Then you create that class com.example.ExecuteActionTokenHandlerFactory with the following code :
public class ExecuteActionTokenHandlerFactory extends ExecuteActionsActionTokenHandler {
#Override
public Response handleToken(ExecuteActionsActionToken token, ActionTokenContext<ExecuteActionsActionToken> tokenContext) {
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), token.getRedirectUri(),
tokenContext.getRealm(), authSession.getClient());
if (redirectUri != null) {
authSession.setAuthNote(AuthenticationManager.SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS, "true");
authSession.setRedirectUri(redirectUri);
authSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
}
token.getRequiredActions().stream().forEach(authSession::addRequiredAction);
UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser();
// verify user email as we know it is valid as this entry point would never have gotten here.
user.setEmailVerified(true);
String nextAction = AuthenticationManager.nextRequiredAction(tokenContext.getSession(), authSession, tokenContext.getClientConnection(), tokenContext.getRequest(), tokenContext.getUriInfo(), tokenContext.getEvent());
return AuthenticationManager.redirectToRequiredActions(tokenContext.getSession(), tokenContext.getRealm(), authSession, tokenContext.getUriInfo(), nextAction);
}
}
Actually it is the same implementation as the upper class, except we removed the following part :
if (tokenContext.isAuthenticationSessionFresh()) {
...
}
which means that if the user did not have a session, which happens when the user is reseting his password, he is redirected to that "info.ftl" page.
As a workaround for problem A, I customize info.ftl template page. I add an ugly inline script to click on the link, redirecting automatically to the update password page.
<#import "template.ftl" as layout>
(...)
<#elseif actionUri?has_content>
<p><a id="yolo" href="${actionUri}">${kcSanitize(msg("proceedWithAction"))?no_esc}</a></p>
<script>document.getElementById('yolo').click()</script>
(...)
It'll do the job until I found a cleaner solution.
At the moment, B problem remains.
The goal is to have a user table that includes their Stripe customer ID, their email they used to authenticate, and the plan they chose to buy from my web application.
It was easy enough to insert in user records once they've registered onto my web application through Cognito. On the front end, I check for when the auth state changes to "signUp", and I call my backend Express API that creates a new User with their email and username.
The tough part is when the customer has made a purchase through Stripe. I used the pre-built Checkout page that creates the UI and logic for me. I set up the event webhooks on my backend so that whenever a customer has purchased an item, I can do my logic there. The problem is that when this webhook is triggered, I don't have information about their email, so I don't know how to find their record in the database and update it with the customer ID information.
Here is my webhook code:
router.post("/webhooks", (req, res) => {
const event = req.body;
switch (event.type) {
case "charge.succeeded": {
console.log("CHARGE SUCCEEDED", event);
}
case "payment_intent.succeeded": {
console.log(event);
const paymentIntent = event.data.object;
const customer_id = event.data.object.customer;
console.log("PaymentIntent was successful!");
// Cognito_User.update(
// {stripe_id : "test_stripe4_id"},
// {returning: true, where: {email: ???} }
// )
break;
}
case "payment_method.attached": {
const paymentMethod = event.data.object;
console.log("PaymentMethod was attached to a Customer!");
break;
}
// ... handle other event types
default: {
// Unexpected event type
return res.status(400).end();
}
}
Technically, I could use the email that the customer submitted to pay for their plan in the Stripe Checkout, but that may not always be the same email they used to log in.
I'm wondering if I can attach additional user information (their email used to login to cognito) to the webhook trigger, OR do I need to ditch the prebuild checkout and create the Stripe payment checkout flow by code?
You could either save the Checkout Session ID once you've created it and before you redirect, or provide an already-existing Client Reference ID from your system when you create the Session.
Either way would work.
I built a custom authentication system using FirebaseAuthentication tokens.
My signup / login flow should work like this:
User presses login button
My server generates the authentication token and sends it to the client
Check if the user already exists (in the 'Auth' table or in my database?)
If true: sign in using FIRAuth.auth()?.signIn(withCustomToken:...
If false: Show a form to to enter custom information (name, etc..)
sign using FIRAuth.auth()?.signIn(withCustomToken:...
save the custom information to my database
My question is: How can I find out if the user has already signed up?
Would a publicly accessible database with only uid's be the way to go?
This is fairly opinion based, but yes, I would use a standalone DB that stores each user's username who has signed up. Then all that is required is a quick web request through a PHP file querying for any rows returned with that username.
The firebase sign in method will feedback in asynchronous callback.
FIRAuth.auth()?.signInWithEmail(email, password: password, completion: { (user , error) in
if let error = error {
print(error.localizedDescription)
return
}
self.signedIn(user)
})
If you haven't sign up yet. The error will print out
There is no user record corresponding to this identifier. The user may have been deleted.
Question says it all. In Firebase, how do I confirm email when a user creates an account, or, for that matter, do password reset via email.
I could ask more broadly: is there any way to send emails out from Firebase? E.g. notifications, etc. This isn't the kind of thing you would usually do client-side.
Update
Note that this was never a very secure way of handling email verification, and since Firebase now supports email verification, it should probably be used instead.
Original answer
I solved the email verification using the password reset feature.
On account creation I give the user a temporary (randomly generated) password. I then trigger a password reset which will send an email to the user with a link. The link will allow the user to set a new password.
To generate a random password you can use code similar to this:
function () {
var possibleChars = ['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?_-'];
var password = '';
for(var i = 0; i < 16; i += 1) {
password += possibleChars[Math.floor(Math.random() * possibleChars.length)];
}
return password;
}
Note that this is happening on the client, so a malicious user could tamper with your logic.
This would need to be done outside of firebase. I store users at /users/ and keep a status on them (PENDING, ACTIVE, DELETED). I have a small service that monitors users of a PENDING status and sends out a confirmation email. Which has a link to a webservice I've created to update the user status to ACTIVE.
[Engineer at Firebase - Update 2014-01-27]
Firebase Simple Login now supports password resets for email / password authentication.
Each of the Simple Login client libraries has been given a new method for generating password reset emails for the specified email address - sendPasswordResetEmail() on the Web and Android, and sendPasswordResetForEmail() on iOS.
This e-mail will contain a temporary token that the user may use to log into their account and update their credentials. This token will expire after 24 hours or when the user changes their password, whichever occurs first.
Also note that Firebase Simple Login enables full configuration of the email template as well as the sending address (including whitelabel email from your domain for paid accounts).
To get access to this feature, you'll need to update your client library to a version of v1.2.0 or greater. To grab the latest version, check out https://www.firebase.com/docs/downloads.html.
Also, check out https://www.firebase.com/docs/security/simple-login-email-password.html for the latest Firebase Simple Login - Web Client docs.
As at 2016 July, you might not have to use the reset link etc. Just use the sendEmailVerification() and applyActionCode functions:
In short, below is basically how you'll approach this, in AngularJS:
// thecontroller.js
$scope.sendVerifyEmail = function() {
console.log('Email sent, whaaaaam!');
currentAuth.sendEmailVerification();
}
// where currentAuth came from something like this:
// routerconfig
....
templateUrl: 'bla.html',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn() // this throws an AUTH_REQUIRED broadcast
}]
}
...
// intercept the broadcast like so if you want:
....
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
if (error === "AUTH_REQUIRED") {
$state.go('login', { toWhere: toState });
}
});
....
// So user receives the email. How do you process the `oobCode` that returns?
// You may do something like this:
// catch the url with its mode and oobCode
.state('emailVerify', {
url: '/verify-email?mode&oobCode',
templateUrl: 'auth/verify-email.html',
controller: 'emailVerifyController',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn()
}]
}
})
// Then digest like so where each term is what they sound like:
.controller('emailVerifyController', ['$scope', '$stateParams', 'currentAuth', 'DatabaseRef',
function($scope, $stateParams, currentAuth, DatabaseRef) {
console.log(currentAuth);
$scope.doVerify = function() {
firebase.auth()
.applyActionCode($stateParams.oobCode)
.then(function(data) {
// change emailVerified for logged in User
console.log('Verification happened');
})
.catch(function(error) {
$scope.error = error.message;
console.log(error.message, error.reason)
})
};
}
])
And ooh, with the above approach, I do not think there's any need keeping the verification of your user's email in your user data area. The applyActionCode changes the emailVerified to true from false.
Email verification is important when users sign in with the local account. However, for many social authentications, the incoming emailVerified will be true already.
Explained more in the article Email Verification with Firebase 3.0 SDK
What I did to work around this was use Zapier which has a built in API for firebase. It checks a location for added child elements. Then it takes the mail address and a verification url from the data of new nodes and sends them forwards. The url points back to my angular app, which sets the user email as verified.
As I host my app files in firebase, I don't need have to take care of any servers or processes doing polling in the background.
There is a delay, but as I don't block users before verifying mails it's ok. Zapier has a free tier and since I don't have much traffic it's a decent workaround for time being.
The new Firebase SDK v3 appears to support email address verification, see here (put your own project id in the link) but it doesn't appear to be documented yet.
I have asked the question on SO here
See #SamQuayle's answer there with this link to the official docs.
As noted by various others Firebase does now support account related emails but even better, as of 10 days ago or so it also supports sending any kind of email via Firebase Functions. Lots of details in the docs and example code here.
I used following code to check the email verification after creating new account.
let firAuth = FIRAuth.auth()
firAuth?.addAuthStateDidChangeListener { auth, user in
if let loggedUser = user {
if loggedUser.emailVerified == false {
loggedUser.sendEmailVerificationWithCompletion({ (error) in
print("error:\(error)")
})
}
else {
print(loggedUser.email)
}
} else {
// No user is signed in.
print("No user is signed in.")
}
}
I used MandrillApp. You can create an API key that only allows sending of a template. This way even thought your key is exposed it can't really be abused unless someone wants to fire off tonnes of welcome emails for you.
That was a hack to get myself off the ground. I'm now enabling CORS from a EC2 that uses the token to verify that the user exists before extending them a welcome via SES.
I created a Meteor app that allows users to sign on using Facebook. To do this, I'm using the accounts-ui and accounts-facebook packages. This works fine.
How do I retrieve the user's email address once they've signed on? I understand that this requires special permission, and so I added email as a "User & Friend's Permission" in the app settings on the Facebook developers site. Following the Meteor documentation, I also set up Account.ui.config like this:
Accounts.ui.config({
requestPermissions: {
facebook: ['email'],
},
passwordSignupFields: 'USERNAME_AND_EMAIL'
});
As expected, when a user of my app signs on using Facebook, it correctly asks them to share their email address. But how do I retrieve it? The user document has only the _id and profile.name.
The Facebook user's email address is stored in [userDocument].services.facebook.email, which is not published to the client, but can be accessed from the server or from the client using Meteor.methods and Meteor.call.
this will add the Facebook profile info to the client side user object.
Accounts.onCreateUser (options, user) ->
if options.profile
user.profile = options.profile
# get profile data from Facebook
result = Meteor.http.get "https://graph.facebook.com/me", {
params: access_token: user.services.facebook.accessToken}
if !result.error && result.data
#if successfully obtained facebook profile, save it off
#the user can access the profile object on the client
user.profile.facebook = result.data;
return user
on the server side the facebook.accesstoken can be accessed... so use it to get the full FB info and save it to the client user object..
ping a Meteor.user() in the console now to get the FB info.
dont think this is exactly best practice in terms of having FB info on the client..