This example uses headers and cookies, which are only accessible when your Function is running @twilio/runtime-handler
version 1.2.0
or later. Consult the Runtime Handler guide to learn more about the latest version and how to update.
When protecting your public Functions and any sensitive data that they can expose, from unwanted requests and bad actors, it is important to consider some form of authentication to validate that only intended users are making requests. In this example, we'll be covering one of the most common forms of authentication: Bearer Authentication using JSON Web Token (JWT).
If you want to learn an alternative approach, you can also see this example of using Basic Auth.
Let's create a Function that will only accept requests with valid JWTs, and reject all other traffic.
In order to run any of the following examples, you will first need to create a Function into which you can paste the example code. You can create a Function using the Twilio Console or the Serverless Toolkit as explained below:
If you prefer a UI-driven approach, creating and deploying a Function can be done entirely using the Twilio Console and the following steps:
https://<service-name>-<random-characters>-<optional-domain-suffix>.twil.io/<function-path>
test-function-3548.twil.io/hello-world
.Your Function is now ready to be invoked by HTTP requests, set as the webhook of a Twilio phone number, invoked by a Twilio Studio Run Function Widget, and more!
First, create a new auth
Service and add two Public Functions using the directions above. These will be named:
/jwt
/bearer
Replace the default contents of each Function with the JWT generation code (Generate a JSON Web Token for Function Authentication) for /jwt
, and the JWT validation snippet (Authenticate Function requests using Bearer Authorization and JWT) for /bearer
respectively. Save both Functions once they contain the new code.
1const jwt = require('jsonwebtoken');23// Hardcoded credentials for this example4const creds = {5username: 'twilio',6password: 'ahoy',7};8// Hardcoded secret for this example. In a real app, you would9// generate this and store it securely as an environment variable10// and access it via context.SECRET or similar11const secret = 'secret_key';1213// Function to generate a JWT token14exports.handler = (context, event, callback) => {15// Retrieve the username and password from the request16const { username, password } = event;17// Prepare a new Twilio response18const response = new Twilio.Response();1920// If the provided credentials are invalid, return 401 Unauthorized.21// In a real app you would check the credentials against your database.22if (username !== creds.username || password !== creds.password) {23response24.setBody('Username or password is incorrect')25.setStatusCode(401);2627return callback(null, response);28}2930// Create a new signed JWT for the user that will expire in 1 day.31// To understand more about JWT and what sub, iss, and these32// other options are, see https://jwt.io/33const token = jwt.sign(34{35sub: username,36iss: 'twil.io',37org: 'twilio',38perms: ['read'],39},40secret,41{ expiresIn: '1d' }42);4344// Set the token as the access_token header and return the response45response.setBody('OK').appendHeader('access_token', token);4647return callback(null, response);48};
We can check that authentication is working first by sending an unauthenticated request to our deployed Function. You can get the URL of your Function by clicking the Copy URL button next to the Function.
Then, using your client of choice, make a GET
or POST
request to your Function. It should return a 401 Unauthorized
since the request contains no valid Authorization header.
curl -i -L -X POST 'https://auth-4173-dev.twil.io/bearer'
Result:
1$ curl -i -L -X POST 'https://auth-4173-dev.twil.io/bearer' -i23HTTP/2 4014date: Tue, 03 Aug 2021 23:01:55 GMT5content-type: application/octet-stream6content-length: 127www-authenticate: Bearer realm="Access to read salaries"8x-shenanigans: none910Unauthorized
Great! Requests are successfully being blocked from non-authenticated requests.
To make an authenticated request and get back a 200 OK
, we'll need to first generate a valid JWT by calling /jwt
. We can then include that token in the Authorization header of our request to /bearer
.
To get a valid JWT, we'll need to submit a valid username and password to the /jwt Function. Right now, these are hardcoded in the Function as twilio
and ahoy
respectively. The JWT generator Function is expecting the username and password to be passed in the body of the request, so you'll need to create a POST
request with a JSON body composed of those values. Using cURL, that would look like this:
1curl -i -L -X POST 'https://auth-4173-dev.twil.io/jwt' \2-H 'Content-Type: application/json' \3--data-raw '{4"username": "twilio",5"password": "ahoy"6}'
and the response would be:
1$ curl -i -L -X POST 'https://auth-4173-dev.twil.io/jwt' \2-H 'Content-Type: application/json' \3--data-raw '{4"username": "twilio",5"password": "ahoy"6}'78HTTP/2 2009date: Tue, 03 Aug 2021 23:16:35 GMT10content-type: application/octet-stream11content-length: 212access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2lsaW8iLCJpc3MiOiJ0d2lsLmlvIiwib3JnIjoidHdpbGlvIiwicGVybXMiOlsicmVhZCJdLCJpYXQiOjE2MjgwMzI1OTUsImV4cCI6MTYyODExODk5NX0.uZzHuN5PpK6qM5wCu01_S8lkFPDpIcxQJq6A7sDr6gc13x-shenanigans: none14x-content-type-options: nosniff15x-xss-protection: 1; mode=block1617OK
The header access_token
contains the valid JWT that was just generated for us. Go ahead and try your request to /bearer
again, but this time including the Authorization header including this JWT:
1curl -i -L -X POST 'https://auth-4173-dev.twil.io/bearer' \2-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2lsaW8iLCJpc3MiOiJ0d2lsLmlvIiwib3JnIjoidHdpbGlvIiwicGVybXMiOlsicmVhZCJdLCJpYXQiOjE2MjgwMzA3ODIsImV4cCI6MTYyODExNzE4Mn0.gBusSFmlRt_o3H3E2UB4GGxjbZJLOOS0bKFXTxAgnlw'
the response should be:
1$ curl -i -L -X POST 'https://auth-4173-dev.twil.io/bearer' \2-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2lsaW8iLCJpc3MiOiJ0d2lsLmlvIiwib3JnIjoidHdpbGlvIiwicGVybXMiOlsicmVhZCJdLCJpYXQiOjE2MjgwMzA3ODIsImV4cCI6MTYyODExNzE4Mn0.gBusSFmlRt_o3H3E2UB4GGxjbZJLOOS0bKFXTxAgnlw'34HTTP/2 2005date: Tue, 03 Aug 2021 23:20:10 GMT6content-type: application/json7content-length: 848x-shenanigans: none9x-content-type-options: nosniff10x-xss-protection: 1; mode=block1112[{"username":"jdoe","salary":"$2000.00"},{"username":"mturner","salary":"$2500.00"}]
At this point, Bearer Authentication is working for your Function!
To make this example your own, you could experiment with the following:
'secret_key'
into an Environment Variable so that it is stored securely and only needs to be changed in one place.