Securing API Keys in Swagger-UI

542 views
Skip to first unread message

j3d

unread,
Aug 20, 2014, 2:33:26 PM8/20/14
to swagger-sw...@googlegroups.com
Dear all,

According to this very interesting article I've just redesigned the authorization mechanism of my REST API. Swagger-UI, as any other client, is just an API consumer that needs a token to interact with the API... and to obtain a token it has to authenticate with a secret API key.

When Swagger-UI (or any other API client) first loads, it has to make two API calls:
  • One to obtain the secret API key by passing its name or ID as the input (this is necessary to avoid hardcoding the secret API key in a JavaScript source where everyone can see it)
  • And another one to obtain a browse token (passing the secret API key as the input)
Once Swagger-UI obtains the browse token, the user can only browse or sign in. Then, when the user eventually signs in, he gets his own token with the proper permissions. This mechanism ensures an user can take actions only through a know client (Swagger-UI, the official webapp, etc.).

As well described in the article, the secret API key changes many times a day... and if the user doesn't browse for more than one hour (or any other configurable amount of time), the token expires and Swagger-UI needs to ask for a new browse token... but since in the meanwhile the secret API key changed, Swagger-UI has to repeat the process described above transparently (i.e. the user really doesn't care that the browse token expired and that the API key associated with Swagger-UI changes every 30 minutes).

So in the end my question is: how do I make Swagger-UI ask for the new API key when the token expires? For example, if an user stops browsing and then comes back after 1 hour and a half... Swagger-UI has to silently ask for the new API key to obtain a new browse token.

Tx,
j3d

j3d

unread,
Aug 23, 2014, 1:53:40 PM8/23/14
to swagger-sw...@googlegroups.com
I'd like to share with you how I solved this issue... and if you have comments or suggestions they are more than welcome ;-)

First of all I've modified index.html like this:

var appId = "swagger-ui"
window.authorizations.add("authToken", new TokenAuthorization(appId, null, null, null));

$('#input_authToken').change(function() {
  var authToken = $('#input_authToken')[0].value;
  if (authToken && authToken.trim() != "") log("added authToken " + authToken);
  window.authorizations.add("authToken", new TokenAuthorization(appId, authToken, null, null));
})

window.swaggerUi.load();


Then, I've implemented this custom Authorization:

var TokenAuthorization = function(appId, token, type, expirationTime) {
  this.appId = appId;
  this.token = token;
  this.type = type;
  this.expirationTime = expirationTime;
  this.locked = false
};

TokenAuthorization.prototype.lock = function() {
  this.locked = true;
};

TokenAuthorization.prototype.unlock = function() {
  this.locked = false;
};

TokenAuthorization.prototype.apply = function(obj, authorizations) {
  var now = Math.round(new Date().getTime() / 1000);
  if (!this.locked && (this.token == null || (this.type == "browse" && this.expirationTime <= now))) {
    var baseUrl = obj.url.split("api-docs")[0] + "auth";
    var appId = this.appId;
    this.lock();
    $.ajax({
      type: "GET",
      url: baseUrl + "/apps/" + appId + "/apikey"
    }).done(function(result) {
      $.ajax({
        type: "POST",
        data: JSON.stringify({ principal: appId, secret: result.apiKey }),
        url: baseUrl + "/apps/credentials",
        contentType: "application/json"  
      }).done(function(result) {
        var token = result.token;
        $.ajax({
          type: "GET",
          headers: { "Authorization": "Token " + token },
          url: baseUrl + "/users/credentials"
        }).done(function(result) {
          e.authorizations.add("authToken", new TokenAuthorization(
            appId, token,
            result.token.header.typ.split('/')[1],
            result.token.claims.exp
          ));
          log("browse token: " + token);
          obj.headers["Authorization"] = "Token " + token;
        })
      })
    });
  } else if (!this.locked) {
    obj.headers["Authorization"] = "Token " + this.token;
  }
  return true;
};

The code above performs three REST calls to eventually get a JWT (JSON Web Token) to be used by the current API consumer (i.e. swagger-ui) to browse the API. Once a browse token expires, a new one is requested automatically... and since the secret API key changes many times a day, the three REST calls have to be performed again. When the user signs in, the browse token is no longer needed the any subsequent request is performed with an authorization token.

j3d
Message has been deleted
Message has been deleted
Message has been deleted

j3d

unread,
Aug 25, 2014, 6:17:39 PM8/25/14
to swagger-sw...@googlegroups.com
Just fixed some minor issues...

var TokenAuthorization = function(appId, token, type, expirationTime) {
  this.appId = appId;
  this.token = token;
  this.type = type;
  this.expirationTime = expirationTime;
  this.locked = false
};

TokenAuthorization.prototype.lock = function() {
  this.locked = true;
};

TokenAuthorization.prototype.unlock = function() {
  this.locked = false;
};

TokenAuthorization.prototype.apply = function(obj, authorizations) {
  if (!this.locked) { if (this.token == null || (this.type == "browse" &&
    this.expirationTime <= Math.round(new Date().getTime() / 1000) + 120)
  ) {
    var baseUrl = window.location.origin + "/auth";
    var appId = this.appId;
    this.lock();
    $.ajax({
      type: "GET",
      url: baseUrl + "/apps/" + appId + "/apikey"
    }).done(function(result) {
      $.ajax({
        type: "POST",
        data: JSON.stringify({ principal: appId, secret: result.apiKey }),
        url: baseUrl + "/apps/credentials",
        contentType: "application/json"  
      }).done(function(result) {
        var token = result.token;
        $.ajax({
          type: "GET",
          headers: { "Authorization": "Token " + token },
          url: baseUrl + "/users/credentials"
        }).done(function(result) {
          e.authorizations.add("authToken", new TokenAuthorization(
            appId, token,
            result.token.header.typ.split('/')[1],
            result.token.claims.exp
          ));
          log("browse token: " + token);
        })
      })
    });
  }}
  if (this.token != null) obj.headers["Authorization"] = "Token " + this.token;
  return true;
};

I hope it helps,
j3d
Reply all
Reply to author
Forward
0 new messages