In this particular example, I think most people would choose option 1. And indeed it feels more natural to me too. Buy why?
Not sure there's going to be a definitive answer to this, but my guess: because password, the entity, has a relatively straight forward representation as a document; HTTP's raison d'etre is document transfer, after all. It's pretty well designed for that.
Additionally, there's a simplicity to password - current state is a simple value type, and consequently there's no confusion about what "modify" should mean. Having only a single method available to mutate the state of the resource doesn't cause any problems because there's only one way you would want to: by completely replacing one representation with another, which is exactly the semantic of the PUT method in HTTP.
Generally speaking, a password change is not idempotent.
Yes, but that's a moot point here; the choice of method in this case is more driven by what should happen if a request is not acknowledged. Do we get to retry? If the message travels through an intermediate, is it allowed to retry the message rather than immediately returning a failure to the client?
(Admittedly, if we are throwing secrets around, we should be using an encrypted channel, which means that the intermediates can't see what's going on)
We aren't sending the request twice because we want the change applied twice, we are sending it twice to ensure that it is delivered at least once.
To ensure that the side effect on the domain doesn't get repeated, use a "conditional put", so that the server can distinguish between two copies of the same request, and two different requests that happen to contain the same representation of the password.
What if we provide a history of password changes as a new end point: /user/{id}/password_changes?
That seems like a reasonable service.
Yup. I would probably lean toward the spelling /user/{id}/password/history
; it feels natural to make the audit log subordinate to the password resource.
And then do we just disallow other http actions to password_changes (PUT, POST, DELETE)? That seems inconsistent... or maybe not?
I don't see a consistency issue. Your domain model is the book of record for password changes; that history is not subject to override by the clients -- the domain reserves that privilege for itself. So the resource is read only. Ta-da?
all that said
We're creating a "password_change". Thus we should be POST to /user/{id}/password_change, or something similar. In this model, the change to the "user" resource is just a side effect of creating a "password_change".
This isn't wrong either; if you twist it around a little bit, you are basically describing AtomPub; you are publishing an entry (the PasswordChanged event) into a feed (the history), and the new "state" of the resource is derived from looking at the history. In other words, RESTful event-sourcing.
It's a bit crooked; the usual idiom is that commands are sent to an aggregate
in the domain model, and the state changes that are caused by the command are published to a stream, which is exposed via an atom feed. In this use case, we turn it around, somewhat, by the fact that the human being, rather than the domain, is the book of record. Maybe.
Jim Webber describes REST application protocols as an analog to a 1950s style office, where you get things done by passing documents into inboxes. You deliver a ChangePasswordRequest to the server, and as a side effect the domain model updates itself, which has the cascading side effect of changing the state of other resources, like the audit log.
That's perhaps overkill for a simple ReplacePassword protocol, but is useful to keep in mind when things get to be more complicated.
This sounds like a case of authentication versus authorization.
JWTs are cryptographically signed claims about the originator of a request. A JWT might contain claims like "This request is for user X" and "User X has an administrator roles". Obtaining and providing this proof through passwords, signatures, and TLS is the domain of authentication - proving you are who you say you are.
What those claims mean to your server - what specific users and roles are allowed to do - is the problem of authorization. The difference between the two can be described with two scenarios. Suppose Bob wants to enter the restricted storage section of his company's warehouse, but first he must deal with a guard named Jim.
Scenario A - Authentication
- Bob: "Hello Jim, I'd like to enter restricted storage."
- Jim: "Have you got your badge?"
- Bob: "Nope, forgot it."
- Jim: "Sorry pal, no entry without a badge."
Scenario B - Authorization
- Bob: "Hello Jim, I'd like to enter restricted storage. Here's my badge."
- Jim: "Hey Bob, you need level 2 clearance to enter here. Sorry."
JWT expiration times are an authentication device used to prevent others from stealing them. If all your JWTs have five minute expiration times, it's not nearly as big a deal if they're stolen because they'll quickly become useless. However, the "session expiration" rule you discuss sounds like an authorization problem. Some change in state means that user X is no longer allowed to do something they used to be able to do. For instance, user Bob might have been fired - it doesn't matter that his badge says he's Bob anymore, because simply being Bob no longer gives him any authority with the company.
These two cases have distinct HTTP response codes: 401 Unauthorized
and 403 Forbidden
. The unfortunately named 401 code is for authentication issues such as missing, expired, or revoked credentials. 403 is for authorization, where the server knows exactly who you are but you're just not allowed to do the thing you're attempting to do. In the case of a user's account being deleted, attempting to do something with a JWT at an endpoint would result in a 403 Forbidden response. However, if the JWT is expired, the correct result would be 401 Unauthorized.
A common JWT pattern is to have "long lived" and "short lived" tokens. Long lived tokens are stored on the client like short lived tokens, but they're limited in scope and only used with your authorization system to obtain short lived tokens. Long lived tokens, as the name implies, have very long expiration periods - you can use them to request new tokens for days or weeks on end. Short lived tokens are the tokens you're describing, used with very short expiration times to interact with your system. Long lived tokens are useful to implement Remember Me functionality, so you don't need to supply your password every five minutes to get a new short lived token.
The "session invalidation" problem you're describing sounds similar to attempting to invalidate a long-lived JWT, as short lived ones are rarely stored server-side while long-lived ones are tracked in case they need to be revoked. In such a system, attempting to acquire credentials with a revoked long-lived token would result in 401 Unauthorized, because the user might technically be able to acquire credentials but the token they're using isn't suitable for the task. Then when the user attempts to acquire a new long lived token using their username and password, the system could respond with 403 Forbidden if they're kicked out of the system.
Best Answer
I think the problem is that HTTP doesn't mean what you want it to mean.
Fundamentally, the semantics of HTTP are that the resources are stored in a flat key value store. Although
/collection
and/collection/item
are hierarchical identifiers (we can use relative resolution to get from one to the other), the resources that they identify are not hierarchical. There's no relationship inferred from the similar spelling of the identifiers.This is why
DELETE /collection
doesn't do anything to your locally cached copy of/collection/item
.Because there is no inferred relationship between the collection and the item, there is no generic vector available for communicating the eTag of the item(s) in the meta-data for the collection.
You can certainly do either of
and the origin server can, at its discretion also change the representation of the other resource, as a side effect.
This isn't to say that you can't communicate the information by hand - there's nothing against the rules about returning a representation of the collection that communicates the appropriate representations of the member items, along with their validators, so that a "smart" client can create the correct requests without needing to get the individual items.
Disclaimer: all analogies are non-normative; what's real is what's described in the specifications.
The semantics of HTTP resources are not quite like those of a file system. For example, if we issue the following command on linux
then one of the effects that we would expect is the removal of
/collection/item
. But that's not true of HTTP!doesn't say anything at all about the resource
/collection/item
. It might be that when the server processes this request, the side effects might affect other resources. But HTTP isn't describing implementations, it is only assigning meaning to the messages. The meaning of the request message is constrained by the target resource only.Another way of saying the same thing: as far as HTTP is concerned, none of these identifiers is "wrong" for an item in a collection.
Some of the server frameworks we use to implement our servers care; for instance, Rails has opinions on spelling. But those are really just implementation details behind the uniform interface.
So yes, in your domain model the project entities and the tracks entities may form a hierarchy, and you might choose spellings for the resource identifiers that reflect that hierarchy, but the semantics of HTTP are those of a flat key value store.
HTTP acts like the second example.