30 Days of Zumo.v2 (Azure Mobile Apps): Day 21 – Calling Custom APIs

I created a Custom API in my last article. However, I didn’t explain how my client could call it. Let’s take a quick example. My custom API returns a JSON object with a jwt token in it. I need to be able to call the API and receive the response, then add the token to the headers of future calls. I’ve got two clients – an Apache Cordova client and a Universal Windows .NET client (the code for which can be used cross-platform for iOS and Android using Xamarin). In addition, the client libraries all use roughly the same API surface so the technique should work for iOS and Android native libraries as well.

Apache Cordova

The method to call in the case of the Apache Cordova (and, by extension, the HTML/JavaScript) SDK is called invokeApi, and you use it like this (in www/scripts/index.js):

    /**
     * Event handler, called when the host is ready to process requests
     * Note: DO NOT USE PLUGINS UNTIL THIS POINT
     * @event
     */
    function onDeviceReady() {
        console.info('Creating Connection to Backend ' + azureBackend);
        client = new WindowsAzure.MobileServiceClient(azureBackend);
        console.info('Calling /api/createKey');
        client.invokeApi('createKey', { method: 'GET' }).then(createKeySuccess, createKeyFailure);

        function createKeySuccess(response) {
            apiToken = response.result.jwt;

            dataTable = client.getTable('TodoItem');
            $('#loginButton').on('click', function (event) {
                event.preventDefault();
                client.login('aad').then(initializeApp, function (error) {
                    console.error('Authentication Failed: ', error);
                    alert('Authentication Failed');
                });
            });
        }

        function createKeyFailure(error) {
            console.error('/api/createKey returned Error: ', error);
            alert('INVALID KEY RECEIVED');
        }
    }

The client.invokeApi takes the API name (which must reside under the /api route) and an options object with the following possible properties:

  • body as a serializable JSON blob (or textual string) for a POST or PATCH operation
  • method is a string for the HTTP method (defaults to POST)
  • parameters is an object for the query string parameters
  • headers for any additional headers

The response is an XmlHttpRequest object – the result property is the decoded JSON response. This gets me halfway there. However, I still need to add the token to the request. To do this, I need a service filter. A service filter wraps around each request, allowing the modification of the request before it is sent. This allows you to add logging, for instance, or – in this case – add an additional header. Firstly I need a service filter function:

    /**
     * Service Filter for adding header to the application
     * @param {object} request the original request
     * @param {function} next the next filter in the list
     * @param {function} callback if not using promises, the callback
     */
    function addApplicationKeyHeader(request, next, callback) {
        console.log('[addApplicationKeyHeader] request = ', request);
        if (typeof apiToken === 'string' && apiToken.length > 1)
            request.headers['X-LOCAL-APITOKEN'] = apiToken;
        next(request, callback);
    }

The highlighted lines are my code within the recipe – I add the token I received from the custom API as a header. Once I’m ready, I call next(request, callback) – this calls the original web request method OR the next filter. Adding the filter so that the client uses it is easy too:

        function createKeySuccess(response) {
            apiToken = response.result.jwt;

            // Add a filter for every future request
            client = client.withFilter(addApplicationKeyHeader);

The withFilter() method returns an updated client object that has the wrapped request object. The additional header is now added to every request and I can use standard processing on the backend to do my validation.

Universal Windows (C#.NET)

My Universal Windows app has a dedicated “service” class called AzureCloudProvider – I can add the request for the custom API to that. As with all things in the heavily-typed world of .NET, I need to create a class for deserializing the response, then I can call InvokeApiAsync to obtain the key:

/// <summary>
        /// The Api Token from /api/createKey
        /// </summary>
        public static string ApiToken { get; set; }

        /// <summary>
        /// Login to the cloud resource
        /// </summary>
        /// <returns>async task</returns>
        public async Task LoginAsync()
        {
            try
            {
                AzureCloudProvider.ApiToken = await GetApplicationToken();
                Debug.WriteLine($"Response from API Token = {ApiToken}");
                await client.LoginAsync("aad");
            }
            catch (MobileServiceInvalidOperationException exception)
            {
                throw new CloudAuthenticationFailedException(exception.Message, exception);
            }
        }
        
        /// <summary>
        /// Determines if the table name is a sync tables
        /// </summary>
        /// <param name="className">The name of the table</param>
        /// <returns>boolean</returns>
        public bool IsSyncTable(string className) => syncTables.Contains(className);

        public async Task<string> GetApplicationToken()
        {
            var response = await client.InvokeApiAsync<ApplicationKeyResponse>("createKey", HttpMethod.Get, null);
            return response.Token;
        }
    }

    /// <summary>
    /// This class is used to decode the response for my custom API
    /// </summary>
    class ApplicationKeyResponse
    {
        [JsonProperty(PropertyName = "jwt")]
        public string Token { get; set; }
    }

If I serialize the ApplicationKeyResponse, I’ll get the JSON output of my /api/createKey custom API. I’ve rigged the code so that the API is called when I login. If there is an error in calling the API, then the usual exceptions get thrown – most notably, a MobileServiceInvalidOperationException will be thrown on a 400 or 500 series response. That exception has a Response property that can be queried to get the actual error code.

The parameters for the InvokeApiAsync method are as follows:

  • string apiName – the name of the API to call
  • HttpMethod method – the HTTP method to call
  • Dictionary params – the additional parameters to use

The next part of the process involves adding the ApiToken as the X-LOCAL-APITOKEN header to every request. This is done through a Delegating Handler. A delegating handler is a basic piece of the System.Net.Http library that is used to process the HTTP request without adjusting the underlying call. As a result, it’s great for things like logging requests and adding additional headers to the existing requests without changing the underlying code. This isn’t an Azure Mobile Apps code fragment per-se – it’s more the standard way of doing this sort of thing in .NET. Here is the delegating handler:

    /// <summary>
    /// A delegating handler for the requests
    /// </summary>
    public class AddApiTokenDelegatingHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (AzureCloudProvider.ApiToken != null)
            {
                request.Headers.Add("X-LOCAL-APITOKEN", AzureCloudProvider.ApiToken);
            }

            // Do the request
            return await base.SendAsync(request, cancellationToken);
        }
    }

The API Token will be shared among all connections, so I made the property static originally. This doesn’t do anything with just a definition. I also have to hook it into the client so that it is used properly. This is done in the definition of the client:

        /// <summary>
        /// Constructor - creates a connection to the backend.
        /// </summary>
        private AzureCloudProvider()
        {
            var appID = "my-app";
            var clientUri = $"https://{appID}.azurewebsites.net";

            Debug.WriteLine($"[AzureCloudProvider#constructor] Initializing connection to {clientUri}");
            this.client = new MobileServiceClient(clientUri, new AddApiTokenDelegatingHandler()) ;

            Debug.WriteLine($"[AzureCloudProvider#constructor] Initialization Complete");
        }

Now that is done, every single request after the custom API will have an X-LOCAL-APITOKEN header that I received from the custom API.

The Backend

The X-ZUMO-INSTALLATION-ID is used to sign the token and then the date and installation ID are used to generate the MD5 checksum. Together, these are encoded in a JWT that expires after four hours. Based on my (admittedly weak) security, I can now update the server to ensure the X-LOCAL-APITOKEN header contains an appropriate API Key. Even if someone steals a token, it will only last for four hours.

Is this secure? Most definitely not. However, I think it is better than nothing and it’s better than a static application key. I’d invest some time in doing something a little more safe before I would rely on this code. I also haven’t done the server-side. To do this, I’d implement an Atttribute that does the check, including checking that the JWT I’ve provided is signed properly, has the appropriate token and has not expired. This is pure ASP.NET code, so I’m not going to cover it here.

The short version of this process, however, is don’t worry about the fact that your Application Key is gone – it was useless anyway. Instead, make sure you know what you are getting into and implement something a little bit more secure. You have the tools to put anything you want in there. Ideally, don’t waste your time on all this and just authenticate all your users.

Next Steps

I’m going to stay with Custom API’s for the next article and look at accessing table and authentication data within a custom API. Until then, you can find the code for todays efforts on my GitHub Repository.

One thought on “30 Days of Zumo.v2 (Azure Mobile Apps): Day 21 – Calling Custom APIs

Comments are closed.