Payload gives the most complete and flexible way to manage user collections that support all the different authentication needs of your sites. It does so using secure HTTP-only cookies and includes support of boilerplate workflows for password recovery and lockout. You can even use Payload to manage multiple auth collections or build in robust role based access controls.
By the end of this article you will have a full understanding of how to implement authentication of users on your frontend NextJS website that can be managed from the admin UI of Payload CMS.
There are two code repositories created for this article that will be used as reference.
You can follow the getting started sections of each repo provided. Setup should only take a few minutes for each. The seeding functions are called from the onInit
function found in the payload.config.ts
of our next-auth-cms
app. By starting the server you should already have both an admin user and a customer, as defined by each role and also a home page.
Since the user has already been created in the onInit
function, we should be able to start both frontend and backend services, open the browser to the locally running site to see the landing page.
We haven't logged in yet and the UI reflects that.
If we were to look at the browser inspection tools showing network traffic and refresh the page, we'd see a call being made to backend that identifies the user state. The route is a GET request to /api/users/me
on the backend which at this point simply returned user: null
.
That call has been defined as a useEffect
built into the Auth Provider component of auth-next-frontend
under /components/Auth/index.ts
. It is helpful to understand that the user in this auth context at any given time may be one of three states: undefined
, before the query has finished, null
as a guest, or the successful user object being returned.
Getting the correct user back from the API request is possible because of the http cookie included in the fetch request which Payload APIs are automatically built and ready to handle for us.
Now that we have enjoyed lurking as a guest, let's go over login.
From the screenshot we have a basic form that does the work of sending the entered credentials to the auth provider by calling the login
from useAuth
.
The login form can be found in the /pages/login/index.ts
of auth-next-frontend
and handles basic error messages but is otherwise left as simple as possible.
Assuming we were successful in entering the user credentials we should be at the /account
page. This form is used to send updates on the user collection of the logged in user. There isn't any API work to be done since Payload already takes care of that for us and can be done with GraphQL or REST as we've done here.
Now we are able to make further calls to the API endpoints from our authenticated frontend. The API will read the user in a stateless way and execute the access control for the user needed for that request.
Checking into the backend code now, the user collection in next-auth-cms
has access
defined so that Bob or an Admin role are the only ones able to read or update the user.
The logout functionality is very similar to login except that we set the user in the frontend state to null
on completion. The backend API will no longer accept requests from the authentication cookie that was originally returned from logging in.
Now that we are no longer authenticated as bob. We can create a new account. Here is the code that handles submitting the create-account
form.
At a high level, we are sending form data to the POST endpoint /api/users
followed by a login with the same credentials.
This is possible because of our access control on users. You may have noticed that the access for create
is set to return true
. That allows a guest user to create a new customer account. Had that not been set, Payload would use the default access control, which allows requests for any logged in user. That wouldn't work for anonymous users of course.
You might be asking, What is stopping a user from creating an account with the role of admin
?
Within the roles
field you can see an additional access
included so that only an admin is able to assign this particular field.
Another way you could handle this is by creating a separate collection for admins or customers outside of roles which would further separate security concerns, that is up to you.
The frontend also has pages built for users to recover a lost password. These forms are built to call the API routes built-in to Payload for the workflow needed to enter the email address, receive the email with a secure link, and complete the reset password.
Payload makes these easily configurable along with account lockouts and other security features. You can read the Authentication documentation for details.
By separating our backend and frontend we also need to have Cross Origin Resource Sharing (CORS) properly configured. Without this, we would be prevented from making frontend calls to the API from a different web address. Payload has a simple configuration for both CORS and you can see it is already configured in the payload.config.ts
file in next-auth-cms
using environment variables defined in .env
. You don't need to configure CORS if your requests are made from the same domain.
Using http-only cookies is a principal detail in how modern REST architecture works. It is an essential tool for building secure and scalable applications. Hopefully this guide helped you understand and implement it for your next big idea.
That's it! We have fully locked down our app and given access to only the right people.
The two template repos built for this blog post are meant to be starting points that anyone can build on or reference. To use them in production be sure to remove the onInit
seeding functions.
Find us on twitter @payloadcms if you liked this article or join our growing Discord community.