I’m trying to expose RESTful API endpoints for creating user accounts to access my API. So, this would be one of the first things my consumers would use. I’m trying to figure out if this is how I should implement the endpoints, so any feedback / guidance (particularly in complying with REST principles) is highly appreciated.
My current thoughts:
POST /users
with parameters name
, email
, and password
to register.
PUT /users/{id}
with parameters name
, email
, and/or password
to update user account.
DEL /users/{id}
to delete account.
I think the above 3 endpoints handles the create, update, and delete part of the user accounts. Now, I’m trying to figure out how to “log in” (essentially, the user needs to submit email
and password
to get an access_token
) and “log out” (where the `access_token” is nullified).
LOG IN
Calls to various other endpoints require Bearer access token. In order to get that access token, I was considering:
GET /users
with parameters email
and password
. The endpoint would return name
, email
, access_token
. For subsequent calls, I’m unsure if I should change the value of access_token
.
GET /users/{id}
with same parameters as GET /users
. This means the {id}
in the path is ignored and useless. This feels weird.
LOG OUT
PUT /users/{id}
with parameter access_token
and the value null
. Essentially, this would nullify the access token, and any further use of the access token in other endpoints would result in a 401 Unauthorized
.
UPDATE
If someone can produce an OpenAPI document showing how this might be implemented, maybe that’ll be easier to understand?
It’s easier to handle when you have different APIs for different matters.
Authentication
POST /auth
or POST /login
doesn’t matter. Use the one you like most for authentication. As for the response, the token you mentioned is OK.
GET /users with parameters email and password. The endpoint would return name, email, access_token.
Since we are speaking about REST, we’ll assume the API to be stateless. So, clients send users’ credentials and the server respond with the auth token (200 OK
) or redirects the client to a new address to follow up with the auth protocol (302 redirect
).
For subsequent calls, I’m unsure if I should change the value of access_token.
Not necessarily. Tokens can live forever (
although it’s not recommended), so that they can be reused over and over. However, the longer they live, the longer is the API exposed to threats, hence vulnerable. The common agreement is then to implement short-living tokens. In 30 seconds or 40 days is up to you, but they should expire eventually. When they expire, clients must ask the server for a new one. A sort of re-login.
Note that, this introduces a new feature: the token life cycle management.
My advice is, don’t reinvent the wheel. There’s a lot written about the subject. A good site to start with is OWASP: REST Security
Logout
POST /logout
. The bearer token is already communicating what system user is involved and what token must be invalidated (expired). This service can be idempotent, doesn’t matter if the token was already expired or it doesn’t exist, the client only needs to know that it went ok (200 OK
)
Users and accounts
For simplicity, don’t mix security management and users management in a single interface.
For example:
/users
/accounts
Each endpoint manages its resource (User
and Account
respectively).
There’s indeed a 1-to-1 relationship between one user
and one account
. We could say that both are the same thing but observed from different standpoints. When we look at the system user from the security standpoint, we “see” Accounts
(privileges, credentials, tokens, permissions, etc). When we look at the very same system user, from the person who is it, we “see” a human being with personal attributes (name, surname, age, etc). In both cases, both things are essentially the same. A system user.
This said
/users
handles the user data and this sort of stuff/accounts
handles the user credentials, grants, roles or/and availability.
The reason for us to do this is segregation of concerns. This way we can attend security without compromising the user’ management and vice-versa. It’s also easier to reason about each subject when we don’t mix them up.
11
I propose the following:
- Have a dedicated /login endpoint. GET /users is more appropriate for obtaining a list of all registered users of your service; GET /users/{id} is more appropriate for obtaining the (public) profile of that user.
- Use POST to obtain an access token, because the login action should not be idempotent or cacheable (both of which a GET would imply). A user should get a new token for any login action so that they can log-in from multiple devices independently; unless that’s explicitly forbidden of course in which case they still would get a new token on every login, the old token would just be invalidated at the same time.
- Similarly: Use POST /logout instead. PUT also implies idempotence, i.e. you should get the same response with the same input when you do it twice. Logout in principle can be idempotent, but only if you can distinguish “this is an old token that once was valid, but is now logged out” from “this is a token that never was valid; someone is trying funny business”. The former can get the same successful response every time /logout is accessed, but the latter should always get a 401.
6
If you expect relying parties to use a browser based OAuth login procedure, it is not particularly difficult to add user creation to the login workflow: the RP doesn’t need to know who it is logging in as until the exchange is complete and it has and it has an access token. This gives you flexibility for expansion in the future without breaking RPs:
- change what information you collect when users register.
- 2FA support
- CAPTCHAs on account creation/login.
If the RP needs additional information about the user, a simple option is to offer a fixed GET endpoint that will return information about the user identified by the access token used to authenticate the request. A PUT or PATCH request on the same endpoint could be used to update the user’s profile.
If you need a way to logout a the current user, I would again suggest using a fixed endpoint that uses the access token to identify which user to expire the tokens of. Adding a user ID to the URI just complicates things.