RESTfully design /login or /register resources?
I was designing a web app and then stopped to think about how my api should be designed as a RESTful web service. For now, most of my URI's are generic and might apply to various web apps:
GET /logout // destroys session and redirects to /
GET /login // gets the webpage that has the login form
POST /login // authenticates credentials against database and either redirects home with a new session or redirects back to /login
GET /register // gets the webpage that has the registration form
POST /register // records the entered information into database as a new /user/xxx
GET /user/xxx // gets and renders current user data in a profile view
POST /user/xxx // updates new information about user
I have a feeling I'm doing a lot wrong here after poking around on SO and google.
Starting with /logout
, perhaps since I don't really GET
anything - it may be more appropriate to POST
a request to /logout
, destroy the session, and then GET
the redirect. And should the /logout
term stay?
What about /login
and /register
. I could change /register
to /registration
but that doesn't alter how my service fundamentally works - if it has deeper issues.
I notice now that I never expose a /user
resource. Perhaps that could be utilized somehow. For instance, take the user myUser
:
foo.com/user/myUser
or
foo.com/user
The end user doesn开发者_开发技巧't require that extra verbosity in the URI. However, which one is more appealing visually?
I noticed some other questions here on SO about this REST business, but I would really appreciate some guidance on what I've laid out here if possible.
Thanks!
UPDATE:
I would also like some opinions on:
/user/1
vs
/user/myUserName
RESTful can be used as a guideline for constructing URLs, and you can make sessions and users resources:
GET /session/new
gets the webpage that has the login formPOST /session
authenticates credentials against databaseDELETE /session
destroys session and redirect to /GET /users/new
gets the webpage that has the registration formPOST /users
records the entered information into database as a new /user/xxxGET /users/xxx
// gets and renders current user data in a profile viewPOST /users/xxx
// updates new information about user
These can be plural or singular (I'm not sure which one is correct). I've usually used /users
for a user index page (as expected), and /sessions
to see who is logged in (as expected).
Using the name in the URL instead of a number (/users/43
vs. /users/joe
) is usually driven by the desire to be more friendly to the users or search engines, not any technical requirements. Either is fine, but I'd recommend you are consistent.
I think if you go with the register/login/logout or sign(in|up|out)
, it doesn't work as well with the restful terminology.
One thing sticks out in particular as not REST-ful: the use of a GET request for logging out.
(from http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods)
Some methods (for example, HEAD, GET, OPTIONS and TRACE) are defined as safe, which means they are intended only for information retrieval and should not change the state of the server. In other words, they should not have side effects, beyond relatively harmless effects such as logging, caching, the serving of banner advertisements or incrementing a web counter. [...]
[... H]andling [of GET requests] by the server is not technically limited in any way. Therefore, careless or deliberate programming can cause non-trivial changes on the server. This is discouraged, because it can cause problems for Web caching, search engines and other automated agents [...]
As for logging out and redirecting, you could have a post to your logout URI give a 303 response redirecting to the post-logout page.
http://en.wikipedia.org/wiki/Post/Redirect/Get
http://en.wikipedia.org/wiki/HTTP_303
Edit to address URL design concerns:
"How do I design my resources?" is an important question to me; "how do I design my URLs?" is a consideration in two areas:
URLs that users will see should not be too ugly and meaningful if possible; if you want cookies to be sent in requests to some resource but not others, you'll want to structure your paths and cookie paths.
If JRandomUser
wants to look at his own profile and you want the URL to be prettier than foo.com/user/JRandomUser
or foo.com/user/(JRandom's numeric user id here)
, you could make a separate URL just for a user to look at their own information:
GET foo.com/profile /*examines cookies to figure out who
* is logged in (SomeUser) and then
* displays the same response as a
* GET to foo.com/users/SomeUser.
*/
I would claim ignorance much more readily than wisdom on this subject, but here are a few resource design considerations:
- Consumer: which resources are meant to be viewed directly in a browser, loaded via XHR, or accessed by some other kind of client?
- Access / identity: does the response depend on cookies or referrers?
Sessions aren't RESTful
Yes, I know. It's being done, usually with OAuth, but really sessions aren't RESTful. You shouldn't have a /login /logout resource primarily because you shouldn't have sessions.
If your going to do it, make it RESTful. Resources are nouns and /login and /logout aren't nouns. I would go with /session. This make creation and deletion a more natural action.
POST vs. GET for sessions is easy. If you are sending user/password as variables, I would use POST because I don't want to have the password sent as part of the URI. It will show up in logs and possibly be exposed over the wire. You also run the risk of having software fail on GET args limitations.
I generally use Basic Auth or no Auth with REST services.
Creating users
It's one resource, so you shouldn't need /register.
- POST /user - Creates a user if the requestor cannot specify the id
- PUT /user/xxx - Create or update a user assuming you know the id beforehand
- GET /user - lists x user ids
- GET /user/xxx - GETs the details of the user with id xxx
- DELETE /user/xxx - Delete the user with id xxx
Which kind of ID to use is a hard question. You have to think about enforcing uniqueness, about reuse of old ids that were DELETEd. For example, you do not want to use those ids as foreign keys on a backend if ids are going to be recycled (if at all possible). You can have a lookup though for external/internal id conversion in order to mitigate backend requirements.
I am going to simply speak from my experience integrating various REST Web Services for my clients, whether it is used for mobile apps or for server to server communication as well as building REST API for others. Here are few observations that I have gathered from other people's REST API as well as those that we built ourselves:
- When we say API, it normally refers to set of programming interface and not necessary the presentation layer. REST is also data centric and not presentation driven. That said most REST return data in the form of JSON or XML and rarely returning a specific presentation layer. This trait (of returning data and not the direct webpage) given REST ability to do multi-channels delivery. Meaning that the same webservice can be rendered in HTML, iOS, Android or even used as server to server combination.
- It's very rare to combine both HTML and REST as a URL. By default REST are thoughts as services and not having presentation layer. It is the job for those who consume the webservices to render the data from the services that they call according to what they want. To that point your URL below does not conform with most REST based design that I've encountered thus far (nor the standards such as those who are coming from Facebook or Twitter)
GET /register // gets the webpage that has the registration form
- Continuing from previous point, it is also uncommon (and I have not encountered) for REST based service to do redirection such as the ones suggested below:
GET /logout // destroys session and redirects to / POST /login // authenticates credentials against database and either redirects home with a new session or redirects back to /login
As REST are designed as services, function such as login and logout are normally returning success/failure result (normally in JSON or XML data format) which then the consumer would interpret. Such interpretation could include the redirection to appropriate webpage as you mentioned
- In REST, the URL signify the actions that is taken. For that reason, we should remove as much ambiguity as possible. While it is legitimate in your case to have both GET and POST that has the same path (such as /register) that perform different action, such design introduce ambiguity in services provided and may confuse the consumer of your services. For example, the URLs such as the one that you introduce below are not ideal for REST based services
GET /register // gets the webpage that has the registration form POST /register // records the entered information into database as a new /user/xxx
Those are some points from what I have dealt with. I hope it could provide some insights for you.
Now as far for implementation of your REST, these are the typical implementation that I have encountered:
GET /logout
Execute logout in the backend and return JSON for denoting the success/failure of the operation
POST /login
Submit credentials to the backend. Return success/failure. If successful, normally it will also return the session token as well as the profile information.
POST /register
Submit registration to the backend. Return success/failure. If successful, normally treated the same as successful login or you could choose to make registration as a distinct service
GET /user/xxx
Get user profile and return JSON data format for the user's profile
POST /user/xxx // renamed to POST /updateUser/xxx
Post updated profile information as JSON format and update the information in the backend. Return success/failure to the caller
I believe this is a RESTful approach to authentication. For LogIn you use HttpPut
. This HTTP method can be used for creation when the key is provided, and repeated calls are idempotent. For LogOff, you specify the same path under the HttpDelete
method. No verbs utilized. Proper collection pluralization. The HTTP methods support the purpose.
[HttpPut]
[Route("sessions/current")]
public IActionResult LogIn(LogInModel model) { ... }
[HttpDelete]
[Route("sessions/current")]
public IActionResult LogOff() { ... }
If desired you could substitute current for active.
I would recommend using a user account URL similar to twitter where the user's account URL would be something like foo.com/myUserName
just like you can get to my twitter account with the URL https://twitter.com/joelbyler
I disagree about logout requiring a POST. As part of your API, if you are going to maintain a session, then a session id in the form of a UUID might be something that can be used to keep track of a user and confirm that the action being taken is authorized. Then even a GET can pass along the session id to the resource.
In short I would recommend that you keep it simple, URLs should be short an memorable.
ReST API itself stands for Representational State Transfer, in which you can you're transferring the resource's state to and fro from your database.
So each verb should be allocated for a resource, whereas in /user/login
might not seem to be the correct example to define login
to be a resource, wherein user
is the actual resource.
Here is what I follow, that might make some sense.
POST /user {userObject} -> To create user resource, i.e, Signup.
POST /user/:username {password: `${user_password}`} -> To identify the user by the username and authenticate using the password, i.e, Login.
GET /user -> To list all users from your table/collection
GET /user/:id -> To get details of a particular user (:id can be replaced with :username, or :email or the primary key you have setup for the user.)
PUT /user/:id -> To Update the user object.
DELETE /user/:id -> To delete the user object.
The above format is as follows: [HTTP Verb] /resource-path {body(if required*)}.
Whereas for logout, again the state comes into play where you are not transporting the session when logging in, instead a JWT/AccessToken, which is a client-side authorization mechanism, which you store at frontend as local-storage, which just needs to be deleted if not expired when logging out.
You won't have a logout path, that needs to be requested on your API, instead get's handled by expiry time or deleted at frontend only.
精彩评论