用户 registration/authentication 在 REST API 上流动

User registration/authentication flow on a REST API

我知道这不是第一次在 Whosebug 中处理该主题,但是,我有一些问题找不到答案,或者其他问题有相反的答案。

我正在做一个相当简单的 REST API (Silex-PHP),最初仅由一个 SPA (backbone 应用程序) 使用。我不想评论这个问题中的所有几种身份验证方法,因为 SO 已经完全涵盖了该主题。我基本上会为每个用户创建一个令牌,这个令牌将附加在每个需要 SPA 身份验证的请求中。所有 SPA-Server 事务都将在 HTTPS 下 运行。目前,我的决定是令牌不会过期。每个会话 expire/tokens 的令牌不符合 REST 的无状态,对吗?我知道安全性有很大的改进空间,但这是我目前的范围。

我有一个代币模型,因此在数据库中有一个 table 用于 FK 为 user_id 的代币。我的意思是令牌不是我的用户模型的一部分。

注册

我有一个 POST /users(不需要身份验证),它在数据库中创建一个用户,returns 是新用户。这符合一个请求一个资源规则。然而,这让我有些疑惑:

现在我的流程如下:

1. Register form makes a POST /users request
2. Server creates a new user and a new token, returns both in the response (break REST rule)
3. Client now attaches token to every Request that needs Authorization

令牌永不过期,保持 REST 无状态。

电子邮件验证

当前的大多数 webapp 都需要在不破坏用户 UX 的情况下进行电子邮件验证,即用户可以在注册后立即使用 webapp。另一方面,如果我按照上面的建议 return 带有注册请求的令牌,用户将立即可以访问每个资源而无需验证电子邮件。

通常我会采用以下工作流程:

1. Register form sends POST /users request.
2. Server creates a new user with validated_email set to false and stores an email_validation_token. Additionally, the server sends an email generating an URL that contains the email_validation_token. 
3. The user clicks on the URL that makes a request: For example POST /users/email_validation/{email_validation_token}
4. Server validates email, sets validated_email to true, generates a token and returns it in the response, redirecting the user to his home page at the same time.

这看起来过于复杂并且完全破坏了用户体验。你怎么样了?

登录

这很简单,目前我是这样做的,如果不对请指正:

1. User fills a log in form which makes a request to POST /login sending Basic Auth credentials.
2. Server checks Basic Auth credentials and returns token for the given user.
3. Web app attached the given token to every future request.

login 是一个动词,因此违反了 REST 规则,但似乎每个人都同意这样做。

注销

为什么每个人似乎都需要一个 /auth/logout 端点?从我的角度来看,单击网络应用程序中的“注销”基本上应该从应用程序中删除令牌,而不是在进一步的请求中发送它。服务器在此没有任何作用。

由于令牌可能保存在 localStorage 中以防止在可能的页面刷新时丢失令牌,注销也意味着从 localStorage 中删除令牌。但是,这仍然不会影响服务器。我知道需要 POST /logout 的人基本上是在使用会话令牌,这再次打破了 REST 的无状态。

记住我

我理解记住我基本上是指在我的情况下是否将 returned 令牌保存到 localStorage。这样对吗?

如果您推荐有关此主题的任何进一步阅读,我将不胜感激。谢谢!

你是对的,SESSION在REST中是不允许的,因此在REST服务中不需要登录或注销,/login、/logout不是名词。

对于身份验证,您可以使用

  1. 基于 SSL 的基本身份验证
  2. 摘要式身份验证
  3. OAuth 2
  4. HMAC等

我更喜欢使用 PUBLIC KEY 和 PRIVATE KEY [HMAC]

私钥永远不会通过网络传输,我不关心 public 密钥。 public 键将用于进行用户特定操作 [谁持有 api 键]

私钥将被客户端应用程序和服务器知道。私钥将用于创建签名。您使用私钥生成签名令牌并将密钥添加到 header。服务器还将生成签名并验证握手请求。

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

现在你将如何获得私钥?你必须手动完成,就像你在你的应用程序上放置 facebook、twitter 或 google api 键一样。

但是,在某些情况下,您也可以像 Amazon S3 那样 return [不推荐] 仅使用一次密钥。他们在注册响应中提供 "AWS secret access key"。

注册

Tokens that expire/tokens per session are not complying with the statelessness of REST, right?

不,这没有错。许多 HTTP 身份验证方案确实有过期令牌。 OAuth2 在 REST 服务中非常受欢迎,许多 OAuth2 实现强制客户端不时刷新访问令牌。

My idea is that at the time to create a new user, create a new token for the user, to immediately return it with the Response, and thus, improving the UX. The user will immediately be able to start using the web app. However, returning the token for such response would break the rule of returning just the resource. Should I instead make two requests together? One to create the user and one to retrieve the token without the user needing to reenter credentials?

通常,如果您按照 REST 最佳实践创建新资源,您不会 return 响应这样的 POST。这样做会使调用更像 RPC,所以我同意你的看法……它并不完美 RESTful。我将提供两种解决方案:

  1. 忽略这个,打破最佳实践。也许在这种情况下这是最好的,如果例外更有意义,有时是最好的做法(经过仔细考虑)。
  2. 如果你想要更多RESTful,我会提供一个替代方案。

假设您想使用 OAuth2(不错!)。由于多种原因,OAuth2 API 并不是真正的 RESTful。我认为使用定义明确的身份验证 API 比使用自己的身份验证更好 RESTful.

这仍然会给您留下在 API 上创建用户的问题,并响应此 (POST) 调用,returning 一个可以使用的秘密作为 access/refresh 令牌。

我的备选方案如下:

您无需拥有用户即可开始会话

您可以做的是在创建用户之前启动会话。这保证了对于以后的任何呼叫,您知道您正在与同一个客户通话。

如果您启动 OAuth2 流程并收到您的 access/refresh 令牌,您只需在 /users 上执行经过身份验证的 POST 请求。这意味着您的系统需要知道两种类型的经过身份验证的用户:

  1. 使用 username/password(`grant_type = 密码 1)登录的用户。
  2. 登录 'anonymously' 并打算事后创建用户的用户。 (grant_type = client_credentials).

创建用户后,您可以将之前的匿名会话分配给新创建的用户实体,因此您无需在创建后进行任何 access/refresh 令牌交换。

电子邮件验证

你的两个建议:

  • 在电子邮件验证完成之前阻止用户使用该应用程序。
  • 允许用户立即使用该应用程序

由应用程序完成。哪一个更合适实际上取决于您的应用程序以及最适合您的方法。用户开始使用不属于他们的电子邮件的帐户是否存在任何风险?如果否,那么可以立即允许用户进入。

这是一个您不想这样做的示例:假设您系统的其他成员使用电子邮件地址将用户添加为朋友,则电子邮件地址是一种身份。如果您不强制用户验证他们的电子邮件,这意味着我可以代表具有不同电子邮件地址的人行事。这类似于能够接收邀请等。这是攻击向量吗?那么您可能要考虑阻止用户使用该应用程序,直到电子邮件得到验证。

您还可以考虑仅阻止应用程序中电子邮件地址可能敏感的某些功能。在前面的示例中,您可以阻止人们在验证电子邮件之前看到来自其他用户的邀请。

这里没有正确答案,这取决于您打算如何使用电子邮件地址。

登录

请只使用 OAuth2。您描述的流程已经非常接近 OAuth2 的工作方式。更进一步,实际上 使用 OAuth2。它非常棒,一旦你克服了理解协议的最初障碍,你会发现它比你想象的更容易,而且相当直接地实现你的 API.

特别需要的位。

大多数 PHP OAuth2 服务器实现都不是很好。他们做的太多了,有点难以整合。自己动手并不难,而且您已经非常接近构建类似的东西了。

注销

您可能需要注销端点的两个原因是:

  1. 如果您使用基于 cookie/session 的身份验证并希望告诉服务器忘记会话。听起来这对你来说不是问题。
  2. 如果你想告诉服务器提前使 access/refresh 令牌过期。是的,您可以将它们从本地存储中删除,这可能就足够了。强制他们在服务器端过期可能会给你一点额外的信心。如果有人能够对您的浏览器进行中间人攻击并且现在可以访问您的令牌怎么办?我可能想快速注销并使所有现有令牌过期。这是一个边缘案例,我个人从未这样做过,但 可能 是你想要它的原因。

记住我

是的,使用本地存储实现 "remember me" 听起来是个好主意。

我最初采用了 /LOGON/LOGOUT 方法。我开始探索 /PRESENCE。它似乎可以帮助我将了解某人的身份和身份验证结合起来。

0 = Offline
1 = Available
2 = Busy

Offline 到其他任何内容都应包括初始验证(也称为要求 username/password)。您可以为此使用 PATCHPUT(取决于您的看法)。