简体   繁体   English

REST API上的用户注册/身份验证流程

[英]User registration/authentication flow on a REST API

I know this is not the first time the topic is treated in StackOverflow, however, I have some questions I couldn't find an answer to or other questions have opposed answers. 我知道这不是第一次在StackOverflow中处理该主题,但是,我有一些问题我无法找到答案或其他问题反对答案。

I am doing a rather simple REST API (Silex-PHP) to be consumed initially by just one SPA (backbone app). 我正在做一个相当简单的REST API(Silex-PHP),最初仅由一个SPA(骨干应用程序)使用。 I don't want to comment all the several authentication methods in this question as that topic is already fully covered on SO. 我不想在这个问题中评论所有几种身份验证方法,因为该主题已经在SO上完全涵盖。 I'll basically create a token for each user, and this token will be attached in every request that requires authentication by the SPA. 我基本上会为每个用户创建一个令牌,这个令牌将附加在需要SPA验证的每个请求中。 All the SPA-Server transactions will run under HTTPS. 所有SPA-Server事务都将在HTTPS下运行。 For now, my decision is that the token doesn't expire. 目前,我的决定是令牌不会过期。 Tokens that expire/tokens per session are not complying with the statelessness of REST, right? 每个会话过期/令牌的令牌不符合REST的无状态,对吧? I understand there's a lot of room for security improvement but that's my scope for now. 我知道有很大的安全改进空间,但这是我现在的范围。

I have a model for Tokens, and thus a table in the database for tokens with a FK to user_id. 我有一个令牌的模型,因此在数据库中有一个表格,用于具有FK到user_id的令牌。 By this I mean the token is not part of my user model. 我的意思是令牌不是我的用户模型的一部分。

REGISTER 寄存器

I have a POST /users (requires no authentication) that creates a user in the database and returns the new user. 我有一个POST / users(不需要身份验证),可以在数据库中创建用户并返回新用户。 This complies with the one request one resource rule. 这符合一个请求一个资源规则。 However, this brings me certain doubts: 但是,这给我带来了一些疑问:

  • 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. 我的想法是,在创建新用户时,为用户创建一个新令牌,立即使用Response返回它,从而改善用户体验。 The user will immediately be able to start using the web app. 用户将立即开始使用Web应用程序。 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? 一个用于创建用户,另一个用于检索令牌而无需用户重新输入凭据?

  • If I decided to return the token together with the user, then I believe POST /users would be confusing for the API consumer, and then something like POST /auth/register appears. 如果我决定将令牌与用户一起返回,那么我相信POST / users会让API使用者感到困惑,然后出现类似POST / auth / register的内容。 Once more, I dislike this idea because involves a verb. 再一次,我不喜欢这个想法因为涉及一个动词。 I really like the simplicity offered in this answer . 我非常喜欢这个答案提供的简洁性。 But then again, I'd need to do two requests together, a POST /users and a POST /tokens. 但话说回来,我需要一起做两个请求,POST / users和POST / tokens。 How wrong is it to do two requests together and also, how would I exactly send the relevant information for the token to be attached to a certain user if both requests are sent together? 一起做两个请求有多么错,而且,如果两个请求一起发送,我如何准确地发送要附加到某个用户的令牌的相关信息?

For now my flow works like follows: 现在我的流程如下:

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

The token never expires, preserving REST statelessness. 令牌永不过期,保留REST无状态。

EMAIL VALIDATION 电子邮件验证

Most of the current webapps require email validation without breaking the UX for the users, ie the users can immediately use the webapp after registering. 大多数当前的Web应用程序都需要电子邮件验证,而不会破坏用户的用户体验,即用户可以在注册后立即使用webapp。 On the other side, if I return the token with the register request as suggested above, users will immediately have access to every resource without validating emails. 另一方面,如果我按照上面的建议返回带有注册请求的令牌,用户将立即访问每个资源而无需验证电子邮件。

Normally I'd go for the following workflow: 通常我会选择以下工作流程:

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.

This looks overcomplicated and totally ruins the UX. 这看起来过于复杂,完全破坏了用户体验。 How'd you go about it? 你怎么样的?

LOGIN 登录

This is quite simple, for now I am doing it this way so please correct me if wrong: 这很简单,现在我这样做,所以如果错误请纠正我:

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 is a verb and thus breaks a REST rule, everyone seems to agree on doing it this way though. login是一个动词,因此打破了REST规则,但每个人似乎都同意这样做。

LOGOUT 登出

Why does everyone seem to need a /auth/logout endpoint? 为什么每个人似乎都需要/ auth / logout端点? From my point of view clicking on "logout" in the web app should basically remove the token from the application and not send it in further requests. 从我的观点来看,点击Web应用程序中的“注销”基本上应该从应用程序中删除令牌,而不是在进一步的请求中发送它。 The server plays no role in this. 服务器在此中没有任何作用。

As it is possible that the token is kept in localStorage to prevent losing the token on a possible page refresh, logout would also imply removing the token from the localStorage. 由于令牌可能保留在localStorage中以防止在可能的页面刷新时丢失令牌,因此注销也意味着从localStorage中删除令牌。 But still, this doesn't affect the server. 但是,这仍然不会影响服务器。 I understand people who need to have a POST /logout are basically working with session tokens, which again break the statelessness of REST. 我理解需要POST /注销的人基本上使用会话令牌,这再次打破了REST的无状态。

REMEMBER ME 记住账号

I understand the remember me basically refers to saving the returned token to the localStorage or not in my case. 我理解记住我基本上是指在我的情况下将返回的令牌保存到localStorage。 Is this right? 这是正确的吗?

If you'd recommend any further reading on this topic I'd very much appreciate it. 如果您对此主题有任何进一步的阅读建议,我将非常感激。 Thanks! 谢谢!

REGISTER 寄存器

Tokens that expire/tokens per session are not complying with the statelessness of REST, right? 每个会话过期/令牌的令牌不符合REST的无状态,对吧?

No, there's nothing wrong with that. 不,这没什么不对。 Many HTTP authentication schemes do have expiring tokens. 许多HTTP身份验证方案都有到期令牌。 OAuth2 is super popular for REST services, and many OAuth2 implementations force the client to refresh the access token from time to time. 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. 我的想法是,在创建新用户时,为用户创建一个新令牌,立即使用Response返回它,从而改善用户体验。 The user will immediately be able to start using the web app. 用户将立即开始使用Web应用程序。 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? 一个用于创建用户,另一个用于检索令牌而无需用户重新输入凭据?

Typically, if you create a new resource following REST best practices, you don't return something in response to a POST like this. 通常,如果您按照REST最佳实践创建新资源,则不会返回响应此类POST的内容。 Doing this would make the call more RPC-like, so I would agree with you here... it's not perfectly RESTful. 这样做会使调用更像RPC,所以我在这里同意你的观点......它并不完全是RESTful。 I'll offer two solutions to this: 我将为此提供两种解决方案:

  1. Ignore this, break the best practices. 忽略这一点,打破最佳做法。 Maybe it's for the best in this case, and making exceptions if they make a lot more sense is sometimes the best thing to do (after careful consideration). 在这种情况下,也许它是最好的,如果它们更有意义的话,做出例外有时是最好的事情(仔细考虑之后)。
  2. If you want be more RESTful, I'll offer an alternative. 如果你想要更加RESTful,我会提供另一种选择。

Lets assume you want to use OAuth2 (not a bad idea!). 让我们假设你想使用OAuth2(不是一个坏主意!)。 The OAuth2 API is not really RESTful for a number of reasons. 由于多种原因,OAuth2 API实际上并不是RESTful。 I'm my mind it is still better to use a well-defined authentication API, over rolling your own for the sake of being RESTful. 我认为最好使用定义良好的身份验证API,而不是为了RESTful而自己滚动。

That still leaves you with the problem of creating a user on your API, and in response to this ( POST ) call, returning a secret which can be used as an access/refresh token. 这仍然会让您在API上创建用户,并在响应此( POST )调用时返回一个可用作访问/刷新令牌的秘密。

My alternative is as follows: 我的替代方案如下:

You don't need to have a user in order to start a session . 您无需拥有用户即可启动会话

What you can do instead is start the session before you create the user. 您可以做的是创建用户之前启动会话。 This guarantees that for any future call, you know you are talking to the same client. 这保证了对于将来的通话,您知道您正在与同一客户通话。

If you start your OAuth2 process and receive your access/refresh token, you can simply do an authenticated POST request on /users . 如果您启动OAuth2进程并接收访问/刷新令牌,则只需对/users执行经过身份验证的POST请求即可。 What this means is that your system needs to be aware of 2 types of authenticated users: 这意味着您的系统需要了解两种经过身份验证的用户:

  1. Users that logged in with a username/password (`grant_type = passsword1). 使用用户名/密码登录的用户(`grant_type = passsword1)。
  2. Users that logged in 'anonymously' and intend to create a user after the fact. 用户“匿名”登录并打算在事后创建用户。 ( grant_type = client_credentials ). grant_type = client_credentials )。

Once the user is created, you can assign your previously anonymous session with the newly created user entity, thus you don't need to do any access/refresh token exchanges after creation. 创建用户后,您可以使用新创建的用户实体分配以前的匿名会话,因此创建后无需进行任何访问/刷新令牌交换。

EMAIL VALIDATION 电子邮件验证

Both your suggestions to either: 你的建议都是:

  • Prevent the user from using the application until email validation is completed. 在完成电子邮件验证之前,请阻止用户使用该应用程序。
  • Allow the user to use the application immediately 允许用户立即使用该应用程序

Are done by applications. 由应用程序完成。 Which one is more appropriate really depends on your application and what's best for you. 哪一种更合适取决于您的应用以及哪种方式最适合您。 Is there any risk associated with a user starting to use an account with an email they don't own? 是否有任何风险与用户开始使用他们不拥有的电子邮件的帐户相关联? If no, then maybe it's fine to allow the user in right away. 如果不是,那么也许可以立即允许用户。

Here's an example where you don't want to do this: Say if the email address is used by other members of your system to add a user as a friend, the email address is a type of identity. 以下是您不希望这样做的示例:假设系统的其他成员使用电子邮件地址将用户添加为朋友,则电子邮件地址是一种身份。 If you don't force users to validate their emails, it means I can act on behalf of someone with a different email address. 如果您不强制用户验证他们的电子邮件,则表示我可以代表具有不同电子邮件地址的人员行事。 This is similar to being able to receive invitations, etc. Is this an attack vector? 这类似于能够接收邀请等。这是一个攻击媒介吗? Then you might want to consider blocking the user from using the application until the email is validated. 然后,您可能需要考虑阻止用户使用该应用程序,直到验证电子邮件。

You might also consider only blocking certain features in your application for which the email address might be sensitive. 您可能还会考虑仅阻止应用程序中某些电子邮件地址可能敏感的功能。 In the previous example, you could prevent people from seeing invitations from other users until the email is validated. 在上一个示例中,您可以阻止人们在验证电子邮件之前看到其他用户的邀请。

There's no right answer here, it just depends on how you intend to use the email address. 这里没有正确的答案,这取决于您打算如何使用电子邮件地址。

LOGIN 登录

Please just use OAuth2. 请使用OAuth2。 The flow you describe is already fairly close to how OAuth2 works. 您描述的流程已经非常接近OAuth2的工作方式。 Take it one step further an actually use OAuth2. 更进一步实际使用OAuth2。 It's pretty great and once you get over the initial hurdle of understanding the protocol, you'll find that it's easier than you thought and fairly straightforward to just implement the bits you specifically need for your API. 它非常棒,一旦你克服了理解协议的最初障碍,你就会发现它比你想象的更容易,而且只是简单地实现你特别需要的API。

Most of the PHP OAuth2 server implementations are not great. 大多数PHP OAuth2服务器实现都不是很好。 They do too much and are somewhat hard to integrate with. 它们做得太多,有点难以整合。 Rolling your own is not that hard and you're already fairly close to building something similar. 滚动你自己不是那么难 ,你已经非常接近建立类似的东西。

LOGOUT 登出

The two reasons you might want a logout endpoint are: 您可能需要注销端点的两个原因是:

  1. If you use cookie/session based authentication and want to tell the server to forget the session. 如果您使用基于cookie /会话的身份验证,并希望告诉服务器忘记会话。 It sounds like this is not an issue for you. 听起来这对你来说不是问题。
  2. If you want to tell the server to expire the access/refresh token earlier. 如果您想告诉服务器提前使访问/刷新令牌过期。 Yes, you can just remove them from localstorage, and that might be good enough. 是的,你可以从localstorage中删除它们,这可能就足够了。 Forcing to expire them server-side might give you that little extra confidence. 强制它们在服务器端到期可能会给你一点额外的信心。 What if someone was able to MITM your browser and now has access to your tokens? 如果有人能够MITM您的浏览器并且现在可以访问您的令牌怎么办? I might want to quickly logout and expire all existing tokens. 我可能希望快速注销并使所有现有令牌过期。 It's an edge case, and I personally have never done this, but that could be a reason why you would want it. 这是一个边缘案例,我个人从来没有这样做,但这可能是你想要它的原因。

REMEMBER ME 记住账号

Yea, implementing "remember me" with local storage sounds like a good idea. 是的,用本地存储实现“记住我”听起来是个好主意。

I originally took the /LOGON and /LOGOUT approach. 我最初采用/LOGON/LOGOUT方法。 I'm starting to explore /PRESENCE . 我开始探索/PRESENCE It seems it would help me combine both knowing someone's status and authentication. 这似乎可以帮助我将知道某人的状态和身份验证结合起来。

0 = Offline
1 = Available
2 = Busy

Going from Offline to anything else should include initial validation (aka require username/password). Offline到其他任何东西都应该包括初始验证(也就是要求用户名/密码)。 You could use PATCH or PUT for this (depending how you see it). 您可以使用PATCHPUT (取决于您的看法)。

You are right, SESSION is not allowed in REST, hence there is no need to login or logout in REST service and /login, /logout are not nouns. 你是对的,REST不允许使用SESSION,因此不需要在REST服务和/ login中登录或注销,/ logout不是名词。

For authentication you could use 对于您可以使用的身份验证

  1. Basic authentication over SSL SSL上的基本身份验证
  2. Digest authentication 摘要式身份验证
  3. OAuth 2 OAuth 2
  4. HMAC, etc. HMAC等

I prefer to use PUBLIC KEY and PRIVATE KEY [HMAC] 我更喜欢使用PUBLIC KEY和PRIVATE KEY [HMAC]

Private key will never be transmitted over web and I don't care about public key. 私钥永远不会通过网络传输,我不关心公钥。 The public key will be used to make the user specific actions [Who is holding the api key] 公钥将用于进行用户特定的操作[谁持有api密钥]

Private key will be know by client app and the server. 客户端应用程序和服务器将知道私钥。 The private key will be used to create signature. 私钥将用于创建签名。 You generate a signature token using private key and add the key into the header. 您使用私钥生成签名令牌,并将密钥添加到标头中。 The server will also generate the signature and validate the request for handshake. 服务器还将生成签名并验证握手请求。

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

Now how you will get private key? 现在你将如何获得私钥? you have to do it manually like you put facebook, twitter or google api key on you app. 你必须手动完成,就像你在你的应用程序上放置Facebook,Twitter或谷歌API密钥。

However, in some case you can also return [not recommended] the key only for once like Amazon S3 does. 但是,在某些情况下,您也可以像Amazon S3一样只返回[不推荐]密钥一次。 They provide "AWS secret access key" at the registration response. 他们在注册响应中提供“AWS秘密访问密钥”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM