First of all, let me say that I agree 100% with @MvdD, that a REST API should not include login/logout semantics. However, as I'm sure you've heard/read OAuth is a headache you really don't want if you aren't "required" to have federated auth!
As other answers have pointed out, there is no one pattern to accomplish this, with several being accepted as answers in previous similar questions (mostly from SO). I'm also assuming you've looked at previous related questions and such. There is also this question (RESTful Authentication) on SO, which details 4 techniques (some more RESTful than others). If you're (seemingly?) happy to transmit the password as cleartext in HTTPS then option #1 of that answer (HTTP basic auth) would work with pretty much any HTTP library your REST tools are wrapping.
If you must...
Since there's a bounty and all (and because it's an interesting challenge), I thought about it, and (after about 1.5 cups of coffee) have had the following idea:
POST
to a "challenge" resource, which then redirects to a resource identifier for that challenge, (and no this is not idempotent).
What am I talking about?
Well, as pointed out here (Table 1, page 3), resource identifiers are different to resources themselves. Ergo, you can RESTful-ly POST
to one URI but be causing a change (i.e. RESTful change, such as CREATE, UPDATE) in a resource not necessarily located there. Let me explain in action...
Authentication Process:
- Client sends
POST /challenges
with payload of username
. This triggers the server to create necessary backend entry in DB table, allocate resources, etc.
- Server responds with a
303 See Other
(which is just a newer version of 302 Moved temporarily
), directing client to /challenges/<some random request code>
.
- Client
GET
's /challenges/<some random request code>
(Note, will be GET, as 303
is used instead of 302
). Which returns (with a 200 OK
) a JSON-encoded challenge (more on this later).
- Client responds to challenge, this time with
POST
to /challenges/<some random request code>
with a hash meeting the challenge as payload.
- Server responds with either
403 Forbidden
(if challenge failed, ergo, wrong password, etc) or a 200 OK
(optionally with a JSON payload containing your session info or user profile or what-have-you).
A word on Challenges: (As promised in step #3)
The challenge given by the server is basically just some task (usually hashing, but possibly a two-way encryption/decryption, etc) given by the server to the client. The server knows (computes) the result (hence why hashes are good, since they're cheap) ahead of time, and compares that with the challenge-responce given by the client.
This can be as simple as some random text (or even static text, like your application name, etc) sent to the client in plaintext (or hash, not important) with the server having worked out the hash of that text (essentially a salt) concatenated with the (hashed, since it's in the DB - and we don't store plaintext passwords in databases) password.
If the client can concatenate this salt with the password (after hashing the password locally) and then hash that concatenation, it can answer the challenge.
Example:
- server: salt = md5("MaryHadALittleLamb") ->
8c20828418ca489f5b949f25f35abaa0
(sends to client in step #3).
- client: hashes the password "
password
" ('username' is a notoriously insecure password selector) to produce 5f4dcc3b5aa765d61d8327deb882cf99
, this is then concatenated with 8c20828418ca489f5b949f25f35abaa0
to produce "5f4dcc3b5aa765d61d8327deb882cf998c20828418ca489f5b949f25f35abaa0
".
- client: hashes this
md5("5f4dcc3b5aa765d61d8327deb882cf998c20828418ca489f5b949f25f35abaa0")
-> 09f3bb66a44153c4053857d4b57fdf3b
and sends to server (step #4).
- server: (having already done the above itself) compares
09f3bb66a44153c4053857d4b57fdf3b
with it's own result, and (if they match) allows username to login (or begin a session, etc).
Note: This is a fairly naive way to implement challenges, as there's no temporal component of the salt it's vulnerable to replay attacks, etc. But you can ask more about that on Security.SO if need be.
All operations you are mentioning are changes to the state of the ticket, so why not model them as operations on Ticket resource?
- Customer registration client creates new ticket:
POST /tickets
- Desk client updates list of tickets, assigning employee to ticket with
PATCH
request to /tickets/next
and JSON body { "employee_id" : "5" }
. Response will be HTTP 301
with Location
header pointing to specific ticket: /tickets/172
This request will also send an async message to following subscribers:
- display service, which shows the queue status on monitors in the room, to notify that queue has been updated and clients shall retrieve new contents.
- call expiration job, which will update the ticket if customer will not arrive in given time.
- If customer arrives at the desk, desk client sends PATCH request to specific ticket
/tickets/172
, which updates ticket status and sends async message to the mentioned subscribers. In this case, call expiration job will be dismissed for this ticket upon receiving the message.
- Upon serving the ticket, desk client will perform PATCH request to update ticket status.
- If customer does not arrive within given time, call expiration job will perform PATCH request to the URI provided in async message before to update ticket status. During processing of this request async message to display service will remove the ticket from the queue display.
All these operations are atomic from client's perspective and details of implementation of atomic operations shall not be exposed. Regarding the logs, they are necessary to maintain API SLA and not as a business function, so they are in fact implementation detail which is not known to the clients and it's safe not to consider logs as side effects.
Best Answer
Since you want to persist things on the server between wizard steps, it seems perfectly acceptable to consider each step as a separate resource. Something along these lines :
By including hypermedia links in the response, you can inform the client on what it can do after this step - go forward or back for intermediary steps, and nothing for the final step. You can see an example of it in Figure 5 here.