Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auth between a website and self-owned API

This has probably been asked before, so a preemptive apology from me.

I built a site and I built an API. The API will also be used by a mobile app in the future. I own both so I'm pretty sure two and three legged OAuth aren't for me. The API has parts that are accessible to the world and other parts that are protected and require a user account. To keep things simple I've just gone with a https + Basic Auth solution (for now). It's all fine and good when testing requests manually to the API (I didn't write tests because I'm a bad person), things work as expected and Basic Auth is fine.

I'm trying to solve the flow of a user logging in with plaintext user and password, send that to the API to authenticate, the API just needs to say yes or no, yet all requests from the site (on behalf of a user) to the API should be signed in some way with their credentials for when they want to POST/GET/PUT/DEL one of the protected resources.

Out of all of the auth resources I've read I'm still confused as to what scheme to use. Storing the plaintext password on the site side so that I can base 64 encode it and send it over the wire seems bad, but it looks like that's what I'd have to do. I've read of digest auth but I'm not sure I get it. Any and all advice is welcome.

like image 639
Justin Avatar asked Dec 27 '13 02:12

Justin


People also ask

How does authentication work in Web API?

Web API assumes that authentication happens in the host. For web-hosting, the host is IIS, which uses HTTP modules for authentication. You can configure your project to use any of the authentication modules built in to IIS or ASP.NET, or write your own HTTP module to perform custom authentication.


2 Answers

This is how I would handle this case;

  1. POST the username and password as a plain text to your api using HTTPS of course.
  2. Then validate it to your database, the best algorithm used nowadays to salt password is bcrypt.
  3. If the user is not valid return 401, or whatever.
  4. If the user is valid, return a JWT token with his profile signed with a Public Key algorithm.
  5. Your fron-end knows the public key so it can decode the JWT but it can't generate a new one.
  6. For every request that needs authentication, you attach an Authentication header, with Bearer [JWT]
  7. A middleware in the backend reads this header and validate it with the private key.

Don't be affraid of JWT there are plenty of implementations for every language and framework and is easier than you might think. A lot of applications are already using JWT already even Google.

Auth0 is an authentication broker that can validate against any identity provider or custom database, and returns JWTs. It provides a clientID that can be used to decode the profile in the front end and a secret to validate the tokens in the backend as well as client side library to do this.

Disclaimer: I work for auth0.

Update: Since you mention node.js and express in comments I will give an example in this technology.

var http = require('http');
var express = require('express');

var jwt = require('jsonwebtoken');  //https://npmjs.org/package/node-jsonwebtoken
var expressJwt = require('express-jwt'); //https://npmjs.org/package/express-jwt

var secret = "this is the secret secret secret 12356";


var app = express();

app.configure(function () {
  this.use(express.urlencoded());
  this.use(express.json());
  this.use('/api', expressJwt({secret: secret}));
});

//authentication endpoint
app.post('/authenticate', function (req, res) {
  //validate req.body.username and req.body.password
  //if is invalid, return 401
  var profile = {
    first_name: 'John',
    last_name: 'Foo',
    email: '[email protected]',
    id: 123
  };

  var token = jwt.sign(profile, secret, {
    expiresInMinutes: 60*5
  });

  res.json({
    token: token
  });
});

//protected api
app.get('/api/something', function (req, res) {
  console.log('user ' + req.user.email + ' is calling /something');
  res.json({
    name: 'foo'
  });
});

//sample page
app.get('/', function (req, res) {
  res.sendfile(__dirname + '/index.html');
});

http.createServer(app).listen(8080, function () {
  console.log('listening on http://localhost:8080');
});

This is an express application with one endpoint that validates username and password. If the credentials are valid it returns a JWT token with the full profile, with expiration 5 hours.

Then we have an example endpoint in /api/something but since I've a express-jwt middleware for everything on /api it requires a Authorization: Bearer header with a valid token. The middleware not only validates the token but also parses the profile and put it on req.user.

How to use this client-side? This is an example with jquery:

//this is used to parse the profile
function url_base64_decode(str) {
  var output = str.replace("-", "+").replace("_", "/");
  switch (output.length % 4) {
    case 0:
      break;
    case 2:
      output += "==";
      break;
    case 3:
      output += "=";
      break;
    default:
      throw "Illegal base64url string!";
  }
  return window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
}
var token;

//authenticate at some point in your page
$(function () {
    $.ajax({
        url: '/authenticate',
        type: 'POST',
        data: {
            username: 'john',
            password: 'foo'
        }
    }).done(function (authResult) {
        token = authResult.token;
        var encoded = token.split('.')[1];
        var profile = JSON.parse(url_base64_decode(encoded));
        alert('Hello ' + profile.first_name + ' ' + profile.last_name);
    });
});

//send the authorization header with token on every call to the api
$.ajaxSetup({
    beforeSend: function(xhr) {
        if (!token) return;
        xhr.setRequestHeader('Authorization', 'Bearer ' + token);
    }
});

//api call
setTimeout(function () {
    $.ajax({
        url: '/api/something',
    }).done(function (res) {
        console.log(rest);
    });
}, 5000);

First, I've an authenticate call with the username and password, I can decode the profile in the JWT to get the user profile and I also save the token to use in every request later on.

The ajaxSetup/beforeSend trick adds the header for every call. So, then I can make a request to /api/something.

As you can imagine this approach doesn't use cookies and sessions so it works out of the box in CORS scenarios.

I'm a big fan of passport.js and I've contributed a lot of adapters and fixes for some other adapter but for this particular case I wouldn't use it.

like image 85
José F. Romaniello Avatar answered Oct 21 '22 18:10

José F. Romaniello


I've been thinking about a similar scenario lately; here's what I did:

  1. SSL + Basic Auth
  2. In the DB (on the API side), generate a random salt (per user), and save the salt and the hashed (password + salt). When a request arrives, throw on the salt and hash it, then compare to what you've saved
  3. Send the password in plaintext - you are using SSL so I think this is okay (this is the part I am most uncertain of)

I don't have a great reason for recommending this but in case you have a reason to use it:

.4. Attach a timestamp to every request and have them expire after a couple of minutes.

The reason you should save salted-and-hashed passwords in your DB is in case someone steals your DB.

Basically I'm putting a lot of faith into SSL, and what I've read tells me that's okay.

like image 1
Rohit Chatterjee Avatar answered Oct 21 '22 18:10

Rohit Chatterjee