Dec 27th, 2023 update: Webtasks have been deprecated.

The default SMS passwordless connection in Auth0 uses Twilio as the transport.

The UI on the Auth0 Dashboard makes it trivial to setup SMS based Passwordless if you have a Twilio account:

But what if you don’t want to, or can’t use Twilio? Worry not! One of the great properties in Auth0 is extensibility. This behavior can be overridden, and you can plug any API to deliver a Time based One Time Password through whatever mechanism you want.

Passwordless is not exactly accurate to describe this type of authentication. There is a Passsword in Passwordless, only it is an ephemeral one, and the user doesn’t need to remember it.

The details of how it works are documented in this article.

And once again, what else could be more convenient than a simple Webtask to act as the go-between your Auth0 account and your transport of choice.

This is another practical example of Webtask as a light-weight API gateway.

1. Create the API on Webtask

The first step is creating a Webtask. For this, I used Webtask Editor, the amazing online code editor.

The simplest possible implementation is one that does nothing:

'use latest';
import bodyParser from 'body-parser';
import express from 'express';
import Webtask from 'webtask-tools';

const server = express();

server.use(bodyParser.json());

server.post('/', (req, res, next) => {
  console.log(req.body);
  res.statusCode = 200;
  res.end();
});

module.exports = Webtask.fromExpress(server);

Here I do two things:

  1. Print the output to the console. Remember Webtask ships with real-time logging so you can immediately see the output.
  2. Return 200, so Auth0 knows that “delivery” was successful.

You can try experimenting later by changing the statusCode to 401 or 500 and see what you get when you request the code.

Saving the Webtask means it is automagically deployed and ready to use. I copied the URL for the next step.

The Webtask URL will look like https://{YOUR ACCOUNT}.run.webtask.io/{NAME OF THE WT}

2. Configuring a Custom SMS Passwordless connection

Custom Passwordless connections can be created in Auth0 through the management API. Perhaps the simplest is to use the API Explorer.

I POST’ed this payload:

{
  "options": {
    "disable_signup": false,
    "name": "sms-custom-wt",
    "strategy": "sms",
    "provider": "sms_gateway",
    "gateway_url": "https://{WT Account}.run.webtask.io/{WT NAME}",
    "from": "+11111111111",
    "syntax": "md_with_macros",
    "template": "Your one time password is > @@password@@",
    "totp": {
      "time_step": 300,
      "length": 6
    },
    "brute_force_protection": true
  },
  "enabled_clients": [
    "2j6ifm......L7pT",
    "68tirH......iHVC"
  ]
}   

The key parameters here are:

  • strategy, that must be sms.
  • provider, that must be sms_gateway.
  • gateway_url is the URL where the request is posted to (the Webtask)

The other parameters are standard (and pretty self-explanatory).

3. Starting a Passwordless authentication request

Now, I can start the auth request. Auth0 offers a few options, depending on the app you are building.

The simplest though is to use the API explorer.

It gives me pre-populated curl commands I can simply paste and run.

curl --request POST \
  --url 'https://{YOUR AUTH0 ACCOUNT}.auth0.com/passwordless/start' \
  --header 'content-type: application/json' \
  --data '{"client_id":"FEL........rRFH", "connection":"sms-custom-wt", "phone_number":"+12223334444", "send":"code", "authParams":{"scope": "openid","state": "YOUR_STATE"}}'

If successful, you should see two outputs:

The result of the curl command:

{
  "_id":"587935d398420f9a11c0c708",
  "phone_number":"+12223334444",
  "request_language":null,
  "phone_verified":false
}

And the output on the Webtask console:

12:17:22 PM: new webtask request 1484338641999.785127
12:17:23 PM: { recipient: '+12223334444',
  body: 'Your code is > 326162',
  sender: '+11111111' }

Now I use the Resource Owner endpoint to complete the transaction:

curl --request POST \
  --url 'https://{YOUR AUTH0 ACCOUNT}.auth0.com/oauth/ro' \
  --header 'accept: application/json' \
  --header 'content-type: application/json' \
  --data '{ "client_id": "FEL......rRFH", "connection": "sms-custom-wt", "grant_type": "password", "username": "+12223334444", "password": "326162", "scope": "openid", "device": "My Phone" }'

with a response that looks like:

{
  "id_token":"eyJ0e....YxpXuLkb9lUss",
  "access_token":"sbeG7stMhr9TBHzr",
  "token_type":"bearer"
}

Voila!

4. Plugging in a real SMS provider

Now that all is set, I can edit the Webtask to plug it in into a real SMS provider.

I have experimented with Clickatell, a provider I used in the past for SMS delivery in South America. But anything with an API is easy to connect to.

'use latest';
import bodyParser from 'body-parser';
import express from 'express';
import Webtask from 'webtask-tools';
import request from 'request';

const server = express();

server.use(bodyParser.json());

server.post('/', (req, res, next) => {

 request.post('https://api.clickatell.com/http/sendmsg',{
    qs: {
      "from": req.body.sender,
      "user": req.webtaskContext.data.SMS_USERNAME,
      "password": req.webtaskContext.data.SMS_PASSWORD, 
      "api_id": req.webtaskContext.data.SMS_API_ID,
      "to": req.body.recipient,
      "text": req.body.body
    },
    headers: {
      'user-agent': 'Auth0 - Passwordless',
    }
  }, (e,s,b) => {
      if(e) return next(e);
      if(s.statusCode != 200) return next(new Error('Send SMS - request failed: ' + s.statusCode));
      if(b.indexOf("ERR") === 0) return next(new Error('Send SMS failed: ' + b));
      res.statusCode = 200;
      res.end();
  });
});

module.exports = Webtask.fromExpress(server);

As I was writing this post, I noticed that Clickatell has updated their docs and API. The code above is taken from an app I wrote a couple years ago. It might not work anymore…

Other regional providers with an API

Know of one not listed here? Let me know!

5. Don’t do this!

For those of you that are paying attention, you will notice that the Webtask above is not secured in any way. Don’t do this.

Auth0 allows you to configure authenticated requests. With this option, Auth0 will add a token to the request that you can check on your API.

Why not make the above mandatory? There are a few cases where auth can be skipped. One (the only one?) of them is a Telco company that deploys Auth0 on their datacenter, and use network level security (e.g. IP restrictions). One could argue that even this is not acceptable, and API security should be enabled always.