質問

I am using JSON web tokens, but this authentication token can be any token from which a unique user can be derived.

I am designing a REST API that allows CRUD operations on resources owned by specific users in our domain. This ownership is determined by a user ID field for each document. Users know their own ID (and thus can send them through their client), and can get their authentication token through single sign-on.

This token is not generated on our servers, it is instead generated on a third-party server (Firebase), and then obtained by both the client and server. The server would then check if the token obtained and then sent by the client is the same as the one expected.

There are two approaches to authenticating here:

  1. Have the client pass the user ID and respective authentication token.
  2. Have the client only pass the authentication token, and derive the user ID from this token server-side.

In my own system the user ID would be passed within the URL itself, and a HTTP header would be used for the token. But the above two approaches can be generalized to any method of passing this information.

The benefits of (1) are the greater granularity for errors. If the authentication token is invalid, the server will be able to log which user the client expected the token to authenticate for. (1) also allows authentication tokens (such as administrative ones) to authenticate for multiple users, while still telling the server to perform CRUD operations for one specific user.

However, (2) seems to have a much simpler interface for the client. This is because in the majority of cases when the token is valid, passing the user ID is duplicated information since it can be derived anyway.

Are there any other factors to consider here?

役に立ちましたか?

解決

The identifier in the URL can be always faked by an attacker. You need a mechanism to ensure that the identifier is actually valid. This is why when modelling endpoints executing a certain operation, the url value is not considered a granting authority and instead a different mechanism is implemented (sessions, JWT tokens,...). And then if you decide you don't trust the URL value anyway, for certain endpoints it's probably the best to drop the value altogether.

With that in mind, in your case you should always extract the user identification from the JWT token and perform role-based authorisation on the identity from the token. Thanks to secret sharing of encryption keys, once you do validate the JWT token on the backend, you are pretty much guaranteed it hasn't been forged and therefore the value of the identity is to be trusted. Now, whether the user whose identity is trusted based on the header value actually has valid permissions to access a certain resource, that's up to business requirements and needs to be modelled separately.

But what if an attacker steals the JWT access token?

To prevent this, issue short-lived access token (in terms of minutes), which can be refreshed with a (long-lived) refresh token. If an attacker steals an access token, they won't be able to do that much harm, if their token remains active only for a short period of time and then expires.

Which approach to ultimately take?

It depends on the use case.

I can imagine, for administration endpoints (on which system administrators operate), you could expose an endpoint accepting a user identification in the URL, but this endpoint would only be available for users with e.g. users:admin role. Not many users would have this role and that would be your way to protect an endpoint where a user A can change information of other users.

For "regular" users, you could have a completely different endpoint, which could e.g. contain the word /self/ in its structure, wouldn't accept an identifier and would act on a user based on the JWT token.

他のヒント

When you evaluate benefits for approach #1 you assume that all URLs contain the userId. This could seem reasonable, after all you design REST URLs for resources and generally the user is the root of most resources, so it's clear that user orders will be fetched with GET /users/:userId/orders, notifications with /user/:userId/notifications and so on

There is another perspective though: the entire application datamodel is specialized for each user, everyone would look at your REST API as a private space without exposing the concept that many users exist. This will produce shorter URLs and less deegrees of freedom for some attacker that wants to exploit URLs (for instance with a dictionary attack). If you are (correctly) concerned with logs, take into consideration that logging the session context (namely the JWT payload) along with the rest of request data will give you all information you need to understand which is the expected behaviour of the application while is performing the request. So you will have just a /orders and /notification method, unless the user is the actual resource that the client has to access. In this case, as already mentioned, a simple technique could be exposing a /me endpoint

What if an admin should access to some user orders? in this case having a totally separate endpoint (/admin-api/users/:userId/orders) could slow down your development but will give you benefits on security side: avoiding to add authorization checks on the same API will prevent vulnerabilities

Concept 1 gives you the most flexibility. If in the future, you need to support users doing operations on other users' data, then you need a way to reference which user it is.

If you go with concept 2 you need to refractory your interface, forcing all clients to be updated.

ライセンス: CC-BY-SA帰属
所属していません softwareengineering.stackexchange
scroll top