在网站和自有API之间进行身份验证

时间:2013-12-27 02:25:01

标签: authentication

之前可能已经提出这个问题,所以我这是先发制人的道歉。

我构建了一个网站,我构建了一个API。移动应用程序将来也会使用该API。我拥有这两个,所以我很确定两个和三个腿的OAuth不适合我。 API包含可供全球访问的部分以及受保护且需要用户帐户的其他部分。为了简单起见,我刚刚使用了https + Basic Auth解决方案(目前为止)。当手动向API测试请求时,这一切都很好(我没有写测试,因为我是一个坏人),事情按预期工作,Basic Auth很好。

我正在尝试解决用明文用户和密码登录的用户流程,将其发送到API进行身份验证,API只需要说是或否,但是来自网站的所有请求(代表API的用户应该以某种方式使用他们的凭据进行签名,以便他们想要POST / GET / PUT / DEL其中一个受保护资源。

在我读过的所有auth资源中,我仍然对使用什么方案感到困惑。在网站端存储明文密码,以便我可以对64进行编码并通过网络发送它看起来很糟糕,但看起来这就是我必须要做的事情。我已经阅读了digest auth但我不确定是否已经知道了。欢迎提出任何建议。

2 个答案:

答案 0 :(得分:10)

这就是我处理这种情况的方法;

  1. 当然,使用HTTPS将用户名和密码作为纯文本发布到api。
  2. 然后验证它到您的数据库,现在使用的最佳算法是盐密码 bcrypt
  3. 如果用户无效,则返回401,或其他任何内容。
  4. 如果用户有效,请返回一个JWT令牌,其配置文件使用公钥算法签名。
  5. 你的前端知道公钥,所以它可以解码JWT,但它不能生成一个新的。
  6. 对于需要身份验证的每个请求,您附加Authentication标题,Bearer [JWT]
  7. 后端的中间件读取此标头并使用私钥对其进行验证。
  8. 不要害怕JWT,每种语言和框架都有很多实现,比你想象的要容易。许多应用程序已经使用JWT甚至谷歌。

    Auth0是一个身份验证代理,可以针对任何身份提供者或自定义数据库进行验证,并返回JWT。它提供了一个clientID,可用于解码前端的配置文件,以及一个秘密,用于验证后端中的令牌以及client side library来执行此操作。

    免责声明:我为auth0工作。

    更新:由于您提及node.js并在评论中表达,我将举例说明此技术。

    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: 'foo@bar.com',
        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');
    });
    

    这是一个快速应用程序,其中一个端点验证用户名和密码。如果凭据有效,则返回具有完整配置文件的JWT令牌,并且到期时间为5小时。

    然后我们在/api/something中有一个示例端点,但由于我在/api上有一个express-jwt中间件,它需要一个带有有效令牌的Authorization:Bearer头。中间件不仅验证令牌,还解析配置文件并将其放在req.user上。

    如何使用此客户端?这是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);
    

    首先,我使用用户名和密码进行身份验证调用,我可以解码JWT中的配置文件以获取用户配置文件,并且还保存令牌以便稍后在每个请求中使用。

    ajaxSetup / beforeSend技巧为每次调用添加标头。那么,我可以向/ api / something发出请求。

    您可以想象这种方法不使用cookie和会话,因此它在CORS场景中开箱即用。

    我是passport.js的忠实粉丝,我为其他适配器贡献了很多适配器和修复程序,但对于这种特殊情况,我不会使用它。

答案 1 :(得分:1)

我最近一直在考虑类似的情况;这是我做的:

  1. SSL + Basic Auth
  2. 在DB(在API端),生成随机盐(每个用户),并保存盐和散列(密码+盐)。当请求到达时,抛出盐并哈希,然后与您保存的内容进行比较
  3. 以明文发送密码 - 您正在使用SSL,所以我认为这是可以的(这是我最不确定的部分)
  4. 我没有充分的理由推荐这个,但万一你有理由使用它:

    0.4。为每个请求附加一个时间戳,并在几分钟后使它们过期。

    您应该在数据库中保存加密密码的原因,以防有人窃取您的数据库。

    基本上我对SSL非常信任,我读过的内容告诉我没关系。