Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any compatible javascript clients? #52

Open
raels opened this issue Sep 18, 2014 · 9 comments
Open

Any compatible javascript clients? #52

raels opened this issue Sep 18, 2014 · 9 comments

Comments

@raels
Copy link

raels commented Sep 18, 2014

This gem looks cool, but I need to have JS clients access my API. Are there any specific JS libs that are known to work with ApiAuth?

@clekstro
Copy link

I've wondered the same thing. While I'm personally not aware of any, because this relies on the creation of a canonical string, I think that any library that follows the same signing algorithm would be compatible.

I think the larger issue is that each client would need access to their private key in order to sign their requests. Where is this stored? Local storage? A cookie? Those things are (likely) ephemeral, meaning the user could easily lock themselves out of your app.

I've been leaning more towards OAuth2 with a username/password flow instead for this very reason, as it's more in line with a typical authentication flow and doesn't require the user to permanently safeguard their token. Should they lose/delete the OAuth2 token, they'd just have to log in again. One could probably implement a similar workflow with this approach, but it doesn't seem as straightforward as OAuth2. Also, combining that with 3rd party authentication tokens might make things even more complex...

Would be happy to get someone else's opinion on this, though.

@mgomes
Copy link
Owner

mgomes commented Sep 21, 2014

There are JS HMAC implementations so one could technically implement the entire algorithm in JS, but there is the issue of the private key. If your implementation uses a separate private key per user, then you could render the private key in the DOM. If you're using JQuery, you would then add the api_auth authorization header to each request. I'm sure other JS frameworks allow you to do the same.

The only catch is to make sure the user that now has access to that private is allowed to see it. If, for example, your private key was account specific and each account had many users with various levels of permissions, you wouldn't want to render that private key in the page.

@mhuggins
Copy link

I've got a similar question as to whether there are clients that will use this scheme in other languages as well (Java, Python, C++, etc.). Is this a custom approach, or one that has a common client implementation?

@bbhoss
Copy link

bbhoss commented Mar 16, 2015

What about the date? You can't set the date in an XHR, and this adds one for you. If you don't know the date, you can't create a proper signature.

@swaathi
Copy link

swaathi commented Oct 13, 2015

I'm facing the same problem. I did a quick search and this came up, https://www.thoughtworks.com/mingle/docs/configuring_hmac_authentication.html. Apparently Thoughtworks also uses this gem, and they've explained how to get around it without library support. I haven't tried it yet, but it looks pretty decent.

@disordinary
Copy link

https://www.npmjs.com/package/api_auth

You'd have to use browserify. Older browsers also have appalling crypto support so I don't know how well it will work - but it's worth a shot..

@Spone
Copy link

Spone commented Mar 31, 2017

Concerning the date issue mentioned by @bbhoss, I manage to circumvent it by using another header (X-Date) in my javascript XHR call, and overriding on the server-side the Date header before calling ApiAuth.authentic?

# When the client cannot set the `Date` HTTP header (for instance XHR),
# we use `X-Date` instead
if !request.headers.include? 'Date' and request.headers.include? 'X-Date'
  request.headers['Date'] = request.headers['X-Date']
end

@ctrlaltdylan
Copy link

I have made a Postman pre-request script to HMAC auth Postman requests, should be easy enough to convert it to node/browser js -

https://gist.github.com/ctrlaltdylan/dd75426527d424bc7a4e93bc6a52ea95

@DaKaZ
Copy link

DaKaZ commented Oct 30, 2018

For those that are looking for it, here is our JS api which works with api-auth:

import HMACSHA1 from 'hmacsha1';
import md5 from 'js-md5';
import axios from 'axios';
import Config from './config';

function InvalidToken(message) {
  this.message = message;
  this.name = 'InvalidToken';
}

function InvalidMethod(message) {
  this.message = message;
  this.name = 'InvalidMethod';
}

const baseHeaders = {
  'Content-Type': 'application/json',
  Accept: 'application/json',
  'X-Date': new Date().toUTCString()
};

const signHeaders = (opts) => {
  const headers = { ...baseHeaders };
  const options = { ...opts };
  if (options.data) {
    if (typeof options.data !== 'string') {
      options.data = JSON.stringify(options.data);
    }
    headers['Content-MD5'] = md5.base64(options.data);
  }

  const canonicalString = `${options.method.toUpperCase()},${headers['Content-Type']},${headers[
    'Content-MD5'
  ] || ''},${options.uri},${headers['X-Date']}`;
  // console.log('canonical string: ', canonicalString);
  const signature = HMACSHA1(options.secretKey, canonicalString);
  headers.Authorization = `APIAuth ${options.id}:${signature}`;
  // console.log('Token:', options.secretKey);
  // console.log('Authorization', headers.Authorization);
  return headers;
};

const api = (auth, uri, method = 'GET', data = null) => {
  if (!(auth && auth.apiToken && auth.apiToken.token)) {
    throw new InvalidToken('Authorization token is invalid or expired');
  }
  const fullURI = `${Config().apiBase}${uri}`;
  const url = `${Config().apiHost}${fullURI}`;
  const instance = axios.create({
    headers: signHeaders({
      method,
      data,
      uri: fullURI,
      secretKey: auth.apiToken.token,
      id: auth.user.id
    }),
    timeout: 60000
  });

  switch (method.toUpperCase()) {
    case 'GET':
      return instance.get(url);
    case 'PUT':
      return instance.put(url, data);
    case 'PATCH':
      return instance.patch(url, data);
    case 'POST':
      return instance.post(url, data);
    case 'DELETE':
      return instance.delete(url);
    default:
      throw new InvalidMethod(`Invalid Method: ${method.toUpperCase()}`);
  }
};

export default api;

Because you need the date/time stamp as part of the calc and CANNOT set that in a browser, we use X-Date and then on the server side have this in our API base controller:

  def modify_headers
    # Browser based XHR requests cannot override the Date header nor can they
    # determine the Date header value before sending the request: making it impossible to
    # generate a reliable HMAC-SHA1 digest. FODlink clients set an X-Date header which is
    # used to generate the digest. When present, we overwrite the Date header with the
    # X-Date header to ensure that HMAC-SHA1 digest can be matched.
    request.headers["Date"] = request.headers["X-Date"] if request.headers["X-Date"].present?
    # on get requests the content-type seems to be striped by Rails
    request.headers["Content-Type"] ||= (request.headers["X-Content-Type"] || "application/json")
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants