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.
_48const jwt = require('jsonwebtoken');_48_48// Hardcoded credentials for this example_48const creds = {_48 username: 'twilio',_48 password: 'ahoy',_48};_48// Hardcoded secret for this example. In a real app, you would_48// generate this and store it securely as an environment variable_48// and access it via context.SECRET or similar_48const secret = 'secret_key';_48_48// Function to generate a JWT token_48exports.handler = (context, event, callback) => {_48 // Retrieve the username and password from the request_48 const { username, password } = event;_48 // Prepare a new Twilio response_48 const response = new Twilio.Response();_48_48 // If the provided credentials are invalid, return 401 Unauthorized._48 // In a real app you would check the credentials against your database._48 if (username !== creds.username || password !== creds.password) {_48 response_48 .setBody('Username or password is incorrect')_48 .setStatusCode(401);_48_48 return callback(null, response);_48 }_48_48 // Create a new signed JWT for the user that will expire in 1 day._48 // To understand more about JWT and what sub, iss, and these_48 // other options are, see https://jwt.io/_48 const token = jwt.sign(_48 {_48 sub: username,_48 iss: 'twil.io',_48 org: 'twilio',_48 perms: ['read'],_48 },_48 secret,_48 { expiresIn: '1d' }_48 );_48_48 // Set the token as the access_token header and return the response_48 response.setBody('OK').appendHeader('access_token', token);_48_48 return 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.
_10curl -i -L -X POST 'https://auth-4173-dev.twil.io/bearer'
Result:
_10$ curl -i -L -X POST 'https://auth-4173-dev.twil.io/bearer' -i_10_10HTTP/2 401_10date: Tue, 03 Aug 2021 23:01:55 GMT_10content-type: application/octet-stream_10content-length: 12_10www-authenticate: Bearer realm="Access to read salaries"_10x-shenanigans: none_10_10Unauthorized
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:
_10curl -i -L -X POST 'https://auth-4173-dev.twil.io/jwt' \_10-H 'Content-Type: application/json' \_10--data-raw '{_10 "username": "twilio",_10 "password": "ahoy"_10}'
and the response would be:
_17$ curl -i -L -X POST 'https://auth-4173-dev.twil.io/jwt' \_17-H 'Content-Type: application/json' \_17--data-raw '{_17 "username": "twilio",_17 "password": "ahoy"_17}'_17_17HTTP/2 200_17date: Tue, 03 Aug 2021 23:16:35 GMT_17content-type: application/octet-stream_17content-length: 2_17access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2lsaW8iLCJpc3MiOiJ0d2lsLmlvIiwib3JnIjoidHdpbGlvIiwicGVybXMiOlsicmVhZCJdLCJpYXQiOjE2MjgwMzI1OTUsImV4cCI6MTYyODExODk5NX0.uZzHuN5PpK6qM5wCu01_S8lkFPDpIcxQJq6A7sDr6gc_17x-shenanigans: none_17x-content-type-options: nosniff_17x-xss-protection: 1; mode=block_17_17OK
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:
_10curl -i -L -X POST 'https://auth-4173-dev.twil.io/bearer' \_10-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2lsaW8iLCJpc3MiOiJ0d2lsLmlvIiwib3JnIjoidHdpbGlvIiwicGVybXMiOlsicmVhZCJdLCJpYXQiOjE2MjgwMzA3ODIsImV4cCI6MTYyODExNzE4Mn0.gBusSFmlRt_o3H3E2UB4GGxjbZJLOOS0bKFXTxAgnlw'
the response should be:
_12$ curl -i -L -X POST 'https://auth-4173-dev.twil.io/bearer' \_12-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2lsaW8iLCJpc3MiOiJ0d2lsLmlvIiwib3JnIjoidHdpbGlvIiwicGVybXMiOlsicmVhZCJdLCJpYXQiOjE2MjgwMzA3ODIsImV4cCI6MTYyODExNzE4Mn0.gBusSFmlRt_o3H3E2UB4GGxjbZJLOOS0bKFXTxAgnlw'_12_12HTTP/2 200_12date: Tue, 03 Aug 2021 23:20:10 GMT_12content-type: application/json_12content-length: 84_12x-shenanigans: none_12x-content-type-options: nosniff_12x-xss-protection: 1; mode=block_12_12[{"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.