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.
For the first question, your approach seems correct to me. You consume a token in the sendsms
entry that can only be generated by the login
endpoint. You could also use a generic token created by login, and not easily guessable (it would be equivalent to a cryptographically generated session ID), used as primary key to a database tuple. There you can store whatever you want (for example the number of SMS still allowed to be sent).
You can also store the "state" in a variable and protect it using, say, HMAC, continuously exchanging it between the server and the client, but you still require some server state to defend against replay attacks on non-idempotent endpoints; at minimum, you need a per-session incremental counter.
The second problem is trickier, and you need first to ask yourself what the difference in behaviour (as seen by the server) would be between the app with its logic, and a malicious user sending repeated requests. You might enforce a minimum delay between requests that is comparable to the maximum rate at which the app can be driven, so that there's no longer an advantage in using tricks rather than going through the app.
Best Answer
We need to think in the best design API starting from the API objective.
Your API objective is:
So, you start with
posts
endpoint:But this
post
can posted to Facebook, LinkedIn, etc and you need the user reference also.You can pass this informations on the body, of course. But:
title
or some platforms has support fortags
. This attributes will be ignored for platforms that not support them. The server will need to figure out at runtime what kind of platform he is receiving and looking if all required fields are there for that platform. The code could easily be a mess.And etc. You can think in a lot of possibilities and particularities of each platform.
As you can see, use a single endpoint is a risk.
So seems good to me separate in different endpoints:
And the URL endpoint and
body
of each one can have his particularities. Divide and conquer.You can understand this as
/platforms/{platform}
, but be careful. Threat this as generic can bring some problems if you need more flexibility on the URL endpoint. Per example, to create a post on Slack you need the platform, user and a channel:Thinking in this way, you can separate the platforms and can change each one without modify the others that are working. The
GET
and another HTTP methods are natural to use in this case too.About the verbosity, is not a problem if this brings clarity about the use of your API.