Skip to content

Detecting Twilio API login failures

When I built out Dog n Bone – a browser phone powered by Twilio, I found that behavior on providing an incorrect accountSid / authToken was not quite what I expected. This post details how I detected Twilio API login failures in Dog n Bone.

Twilio uses ClientCapability tokens to grant access to API features. The back end obtains a ClientCapability object using a Twilio accountSid and authToken. It sets scopes on the ClientCapability to grant only necessary permissions on that account. API requests in the front end authenticate using the JWT created from the CapabilityToken. This mechanism allows the front end to authenticate to the API without exposing the Twilio accountSid / authToken.

What I expected

The examples provided by Twilio show backend (Javascript) code for obtaining a CapabilityToken like this:

// put your Twilio API credentials here
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const authToken = 'your_auth_token';

// put your Twilio Application Sid here
const appSid = 'APXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

const capability = new ClientCapability({
    accountSid: accountSid,
    authToken: authToken,
});
capability.addScope(
    new ClientCapability.OutgoingClientScope({ applicationSid: appSid })
);
capability.addScope(new ClientCapability.IncomingClientScope('joey'));
const token = capability.toJwt();

I assumed that if I provided an incorrect accountSid / authToken, it would throw an error or return an empty / null value. This assumption is incorrect. If you provide an incorrect accountSid / authToken, the Twilio API returns a valid ClientCapability. You can add scopes and you can get a JWT token from it.

Things only go wrong when you try to use it.

What actually happens

All browser audio is handled by the Twilio.Device object. Before you can use it, it must be setup with a capability token JWT. I had assumed that if I created a JWT from an incorrect Twilio accountSid / authToken, the call to setup would fail with an exception. Again, this is incorrect. The API is asynchronous so it does not throw an exception immediately.

Instead, the API creates a Twilio.Device but its initial state is ‘offline’. So if you want to check your accountSid / authToken are correct, you must monitor the Twilio.Device state.

Detecting Twilio API login failures

One way of detecting Twilio API login failures is to attach a listener to Twilio.Device. Specifically, you need to listen for the ‘offline’ event. Dog n Bone is a ReactJS application so the code looks like this:

class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            deviceState: '', // 1
            deviceErrorCode: '',
            deviceErrorMessage: ''
        };
    }
    

    captureDeviceStateChange = (state) => { // 2
        Device.on(state, obj => {
            if (state === 'error') {
                this.setState({
                    deviceState: 'error',
                    deviceErrorCode: obj.code,
                    deviceErrorMessage: obj.message
                });
            } else {
                this.setState({
                    deviceState: state
                });
            }
        });
    };

    componentDidMount() { // 3
        this.captureDeviceStateChange('cancel');
        this.captureDeviceStateChange('connect');
        this.captureDeviceStateChange('disconnect');
        this.captureDeviceStateChange('error');
        this.captureDeviceStateChange('incoming');
        this.captureDeviceStateChange('offline');
        this.captureDeviceStateChange('ready');
    }
    
    handleLogin = (capabilityToken) => {
        this.setState({token: capabilityToken});
        Device.setup(capabilityToken); // 4
    };
    
    render() {
        return (
        ...
        );
    }
}

There’s a lot going on here and it may be easier to understand in context. From the top though, important bits are:

  1. Maintain the device state as a ReactJS state attribute. We want to update it when the Twilio.Device informs us of a new state.
  2. A method to capture device state updates using the Device.on event handler.
  3. Register for updates on all of the documented device states.
  4. On login, call Device.setup with our capabilityToken which may or may not be associated with valid credentials.

If our capability token is associated with bad credentials, the flow is:

  1. Device.setup attempts to start the device then;
  2. the device setup fails asynchronously. Device.setup returns without fuss but the Device state is immediately ‘offline’. Twilio notifies us of the failure by calling the Device.on(‘offline’) event handler.

If the credentials are good though, the Device immediately becomes ‘ready’.

That means we can listen for ‘offline’ or ‘ready’ state to determine if the API credentials are correct.

An alternative method

The above is an asynchronous, front-end way of checking the creds. Alternatively, we could verify the credentials synchronously in the back-end. The trick is to make an API call (any call) that will fail if the credentials are incorrect. If the API call fails, do not create the CapabilityToken or JWT. As an example, you could query your Twilio accounts like this:

const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const authToken = 'your_auth_token';
const client = require('twilio')(accountSid, authToken);
client.api.accounts(accountSid).fetch();

If this returns anything, you’ve proven that the authToken is good and you can create a CapabilityToken from it. If instead it throws an exception, you can send the error to the front end to interpret as a bad credentials error.

This method is a bit of a kludge – we don’t really want anything from the API. But it may be more suitable than the front-end solution in some cases.

Published inWeb Technologies

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *