I’m building a small app to post stuff online—just text, for now. You’ve probably heard of (and used) Medium’s email sign in, where they send you a link that lets you sign into the app (or the browser). This is the way I did it.
The code is on GitHub, and to begin with, let’s take a look at this function:
function signin(username, email) {
var user;
var bucket = ‘constellational-meta’;
var token = {
id: randomString(),
secret: randomString()
};
return checkEmail(username, email).then(function(details) {
user = details;
// Bcrypt temporary token
return bcrypt.hashAsync(token.secret, 10);
}).then(function(hash) {
if (!user.tempTokens) user.tempTokens = {};
user.tempTokens[token.id] = {hash: hash, created: Date.now()};
// Store bcrypted hash of temporary token
return putJSON(bucket, username, user);
}).then(function() {
// Send email
return sendEmail(username, email, token);
});
}
Basically, you create a temporary token, store its hash and send it via email. The email is sent using Mandrill, in this function:
function sendEmail(username, email, token) {
var escapedToken = encodeURIComponent(JSON.stringify(token));
var link = APP_URL + ‘signin?token=’ + escapedToken;
var message = {
html: ‘<p>Hi there!</p><p>Click <a href="%E2%80%99" link>here to sign in</a></p>,
subject: ‘Sign in to Constellational’,
from_email: ‘signin@constellational.com’,
from_name: ‘Constellational Sign In’,
to: [{ email: email, type: ‘to’ }],
headers: { ‘Reply-To’: ‘arpith@constellational.com’ }
};
return new Promise(function(resolve, reject) {
mandrill_client.messages.send({
message: message
}, function(res) {
if (res[0].reject_reason) reject(res[0]);
else resolve(res[0]);
}, function(err) {
reject(err);
});
});
}
That code looks messy, but all it’s doing is encoding the token (after converting it into a string) as a URI component, appending it to the app’s url and including it in the message sent via Mandrill.
The last few lines are in there because I couldn’t promisify Mandrill’s send message function.
And that’s about it! Let me know if you have a better way of doing this - I’m online everywhere!
Bonus: This is the function that generates random strings, based on this question on StackOverflow (using this package that url-escapes base64 strings)
function randomString() {
var randomBytes = crypto.randomBytes(48);
var randomBase64String = randomBytes.toString('base64');
return base64url.escape(randomBase64String);
}
That’s all folks!