An architecture pattern to add user-based authentication to APIs having only API-key based authentication. Warning: this is a work around, so it’s not great, but it does the job.
We recently came across a customer who wants to use ML Studio and its Excel add-in. But they have this crazy idea that they’d like a bit more than just a single API key to access it.
ML Studio deploys its models onto a service for consumption. You are then given an API key and a URL to be used. There are two API keys1 only, so any user given a key can then use it, possibly leak it, continue its use after they left the company, etc. As the service is publicly accessible, anyone with the internet and the key can consume it. Therefore you might want to limit the distribution of this key to mitigate the risk of its leaking. This is not a problem when the service is consumed by other services, in which case users don’t know the key, only the consuming service does. but ML Studio also offers an Excel Add-in, which our customer wants to use, and which expects the API key to be provided.
This customer has higher expectations when it comes to security, such as being able to deactivate a user, logging-out users after a period inactivity, and such things you get from using Active Directory.
The difficulty here is that we are constrained on both sides: by the format consumed by the Excel add in, and by the format consumed by the service2.
So we came up with the following work-around: add an authentication proxy, whose job is just to verify that you are allowed to use the service, and then proxy calls to the service:
This patterns is composed of two main parts:
- an authentication front-end, to which users need to authenticate before they can use the service. This front-end is deployed to an Azure Web App, with Azure AD authentication turned on. This front-end provides the user with a unique user-key (a GUID), which is stored into a table. When users log-in, this key becomes active, then after it stops being used for a configured threshold, it is deactivated, and users need to log back-in to reactivate it.
- an authentication proxy, to which you point the Excel add-in to. To a consumer, the proxy behaves almost exactly as the ML studio service does. But it also verifies that the user key is valid, and activated. Then it replaces the user key by the API key, and forwards the call to the actual ML studio service, and forwards back the response to the Excel add-in3. If the user key is invalid, or if it’s deactivated, then it returns a 403 with a friendly error message inviting users to log-in on the authentication front-end. On successful calls, the proxy extends the user key validity, so that users don’t get logged-out while using it.
So in basic terms, to use the add-in, users go to that authentication front-end website, log-in using their Active Directory credentials. They get the API key and URL from there and use that with the Excel Add-in. After some time of inactivity, the key is deactivated. They just need to log-back-in to reactivate their key.
Once again, this won’t replace an authentication solution, and one might want to take a step back and consider why this is happening - whether this is a legitimate use, or it is just transpiring that a service is not being used for what it’s made. But if this is a legitimate use, then this work-around might work.
Why 2? To support key rotation. ↩
Why not using API management for that purpose? Because it’s not built for that. The default user management built in APIM is going to be a hassle for such use. What the customer needs there really is AAD authentication, which is achieved through OAuth 2 in APIM, and would require significant work on the Excel Add In side. The amount of customization required in APIM to get there just tells you it’s not meant for such work-around scenarios. ↩
It actually also massages the data a bit. ML Studio service returns an Open API specification that contains the URL to the service, so we need to replace that URL with the URL to the authentication proxy. The add-in also expects certain headers to be set on error, so it does that as well. ↩