This project explores Auth0 user authentication with a simple serverless blog site. Serverless means "backend as a service", so in this case we're using Auth0 for user authentication and a custom Laravel API for storing blog data. On the frontend we use Next.js (with Tailwind CSS) which will interface between users and the APIs.
NB; currently auth0/nextjs-auth0
has no way to refresh user data (stored in session cookie) without logging back in. This prevents the ability to see instant changes on /settings
page. A pull req seeks to address this. Keep checking in.
- Next.js - Laravel API - Auth0 prototype
- Table of contents
- Features
- TODO
- Dependencies
- Getting started
- Reference material
- Answers
- Auth0: What is a tenant?
- Auth0: What is an application?
- Auth0: What is a scope?
- Auth0: What does an Auth0 API do?
- Auth0: How can I prevent new users from registering?
- Auth0: How can I update user props?
- Auth0: How can I add username to the idToken payload object?
- Auth0: How can I add username to the idToken payload object?
- Auth0: What is the difference between an ID Token and Access Token?
- Next: Where is my user_id?
- Laravel: How can I get user information from Auth0 token in API request?
- Resources
- Auth0: Sign in |
/api/login
- Auth0: Sign out |
/api/logout
- Auth0: Register an account |
/api/login
- Next: Authed users can change account data |
/settings
- Next: Authed users can create new posts |
/posts/create
- Next: Authed users can update their posts |
/posts/{post}/edit
- Next: Authed users can delete their posts |
/posts
- Next: View all posts created by everyone |
/posts
- Next: View a post |
/posts/{post}
- Next: View all users |
/users
- Next: View a user and their posts |
/users/{user}
- Laravel API: Custom user provider stores Auth0 users in Laravel db
- Laravel API: List, create, read, update and delete all posts
- Laravel API: Update user info
- allow users to delete their posts (on Home / Posts / Users pages)\
- show edit buttons below owned posts (on Home / Posts / Users pages)
- Standardise the
user
andauthed
objects - Finish this README
- Add tests
- Composer for Laravel packages.
- Node for npm packages and serving next.js.
- An Auth0 account (free tier suffices).
Download the NLAP repo from Github if you haven't already.
$ git clone https://github.com/fa-repo/next-laravel-auth0-prototype.git .
Navigate to the /laravel
folder and install php packages.
composer install
And install npm packages in the /next
folder.
npm install
If you don't already have an account, head over and set one up now.
After logging into your account we will need to create a tenant. A tenant is basically a centralised repository of users which we can fill from a whole range of sources; like our own database, auth0 register page of third party 0auth2 services like Google / Github. We can then tap into this repository to log users into anything we want.. maybe a website and mobile app combo? or a single sign on service for a range of unrelated apps?? The options are numerous. Anyway, name the tenant carefully as it can't be changed later.
Head over to the applications page and create a Regular Web Application
called Next Client
.
The settings in this application will allow us to connect our Next client to Auth0.
On the settings tab of the new app, configure the fields:
Allowed Callback URLs
: Auth0 will redirect us to this route after we log in.
- Local example:
http://localhost:3000/api/callback
- Production example:
https://myapp.com/api/callback
Allowed Logout URLs
: Auth0 will redirect us to this route after we log out.
- Local example:
http://localhost:3000/
- Production example:
https://myapp.com/
Include the username in the idToken payload by heading to the rules
page and creating a new rule called Transform user claims
. Paste the snippet below: Read more on including a username in payload here.
function (user, context, callback) {
const blacklist = [ 'nickname' ];
// Copy nickname claim to name claim
context.idToken.name = user.username;
// Remove nickname claim
Object.keys(user).forEach(function(key) {
if (blacklist.indexOf(key) > -1) {
delete user[key];
}
});
auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
.then(function(){
callback(null, user, context);
})
.catch(function(err){
callback(err);
});
}
Head back to the applications page and create a Machine to Machine
app called User Manager
.
Connect this app to Auth0 Management API
and enable update:users
scope
Duplicate .env.example
and rename to .env
.
Configure AUTH0_*
env (used to authorise users and read personal info):
// Grab these values from the `Next Client` app settings page.
AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN
AUTH0_CLIENT_ID=YOUR_AUTH0_CLIENT_ID
AUTH0_CLIENT_SECRET=YOUR_AUTH0_CLIENT_SECRET
// An audience identifies the API we want to interact with. In
// our case we want to use the `Auth0 Management API` found
// at `http://{tenant}.{country}.auth0.com/api/v2/`. Don't
// forget that trailing `/`!
AUTH0_AUDIENCE=https://{tenant}.{country}.auth0.com/api/v2/
// Don't know why these scopes are necessary. They just are for
// `@auth0/nextjs-auth0` to work.
AUTH0_SCOPE=openid profile
Configure AUTH0_MANAGER_*
env (used to perform exec. actions on auth0 tenant):
// Grab these values from the `User Manager` app settings page.
AUTH0_MANAGER_CLIENT_ID=
AUTH0_MANAGER_CLIENT_SECRET=
Configure SESSION_COOKIE_*
env:
You have to provide a random string of at least 32 characters here. This tool should do the job: browserling.com/tools/random-string.
SESSION_COOKIE_SECRET=viloxyf_z2GW6K4CT-KQD_MoLEA2wqv5jWuq4Jd0P7ymgG5GJGMpvMneXZzhK3sL
// 7200 seconds = 2 hours
SESSION_COOKIE_LIFETIME=7200
Configure other vars:
// The URL you will be redirected to after logging in
REDIRECT_URI=http://localhost:3000/api/callback
// The URL you will be redirected to after logging out
POST_LOGOUT_REDIRECT_URI=http://localhost:3000/
Serve the Laravel API in the laravel
folder.
php artisan serve
Switch on the Next client in the next
folder.
npm run dev
-
/
| everyone- Show a welcome message to guests
- Show a custom message to authed users
- Display the latest 5 posts pooled from all users
- Displayed the latest 5 users to register
-
/posts
| everyone | view a list of all posts created by all users -
/posts/{post}
| everyone | view a post -
/posts/create
| requires auth | create a new post -
/posts/{post}/edit
| requires auth | edit one of my posts -
/posts/{post}/delete
| requires auth | delete one of my posts -
/users
| everyone | view a list of all users -
/users/{user}
| everyone | view a users profile and posts -
/users/ssr-{user}
| requires auth | view profile / posts (rendered on server) -
/settings
| requires auth | update auth0 info associated with the user -
/api/login
| requires guest | sign in with Auth0 -
/api/callback
| requires guest | sets cookies after redirect from Auth0 login -
/api/logout
| requires auth | sign out of Auth0 - GET |
/api/me
| requires auth | get logged in users info - PATCH |
/api/me
| requires auth | update logged in users info
A tenant is a walled-off database of users that you can configure any number of connections (called apps) to have access too. So for example, if three apps (website A, website B and mobile app) were connected to the same tenant, a user would only need to register in one place to gain access to all three.
- An
application
configures a connection between a client and auth0. - An
application
has four types to pick from to cater to varying use cases. Native app
: used for mobile, desktop and smartphone type apps.SPA web app
: used for web apps that communicate mostly through APIs.Regular web app
: used for web apps that perform most logic on the server.M2M app
: used for backend services that require no human interaction.NB
: all presets require some human interaction exceptM2M
.NB
: features vary between types to cater to the security demands of each client.NB
:native
,SPA
andregular
apps can pool users from multiple sources. Like custom databases, Single Sign-On services (SSO) and social networks.NB
:M2M
apps issue tokens that can be used with APIs authed in the APIs tab.- Resources:
A scope
defines the actions that an application can do on a users behalf. Like the permissions system on the android app store. Glossary entry.
An Auth0 API is a piece of middleware that protects your APIs.
- Open your dashboard.
- Navigate to
Connections
>Database
page. - Select the database used for this prototype.
- Scroll down (on the Settings tab) to
Disable Sign Ups
option and switch off.
-
For backend apps:
- Create an Auth0
M2M
application
calledUser Manager
. - Connect
User Manager
to Management API w/update:users
scope. - On the backend: request an access token and store the token in cookies.
- Use this token whenever you want to update the user.
- Create an Auth0
-
For SPAs (haven't tried yet):
- Get Management API Tokens for Single-Page Applications
- NB; because SPAs can't store tokens securely, scopes are restricted to updating metadata.
Auth0 won't allow us to create a username
claim. The only claims we can set are those in the OIDC spec or rather ugly looking namespaced claims.
To make any changes, we have to create a rule on the rules page. I find the
Add persistent attributes to user
template to be most useful.
OIDC claims: while we can't add any top-level props outside of the OIDC spec, we could transform one of the objects that IS IN the OIDC spec. I'd be inclined just to add username
to name
or nickname
instead:
function (user, context, callback) {
// add username to `name` claim
context.idToken.name = user.username;
auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
.then(function(){
callback(null, user, context);
})
.catch(function(err){
callback(err);
});
}
Our other option is to create an ugly looking namespace claim. As described in the docs, this is to ensure that it is globally unique and doesn't clash with claims from other resources or the OIDC spec. From the example: Yes indeed you have to access the property on the client like user['https://nlap/username'].
function (user, context, callback) {
// add username to `https://nlap/username` claim
context.idToken['https://nlap/username'] = user.username;
auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
.then(function(){
callback(null, user, context);
})
.catch(function(err){
callback(err);
});
}
- You can't add the
username
claim but you can add a namespaced version of the claim. - NB; Unfortunately Auth0 prevents us from doing this to ensure no claims can accidentally write over reserved claims or claims from other resources.
- NB; We CAN create custom claims but they require strict namespacing rules which look kinda ugly to ensure they are globally unique. So instead of
user.username
you'd have to useuser['https://nlap.com/username']
. - To set up this all up, head to the
Rules
page. - Create a new rule and select
Add persistent attributes to the user
template. - Customise to look something like this:
function (user, context, callback) {
user.user_metadata = user.user_metadata || {};
user.user_metadata.username = user.nickname;
context.accessToken['https://nlap.com/username'] = user.user_metadata.username;
context.accessToken.username = user.user_metadata.username;
auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
.then(function(){
callback(null, user, context);
})
.catch(function(err){
callback(err);
});
}
- Save and log back in with your Auth0 account client-side to retrieve this new claim in your
ID Token
.
ID Token
: is a JSON Web Token (JWT) that contains info about a user.Access Token
: is a token used to gain access to APIs- Tokens documentation
user_id
comes bundled in the user object under the sub
key.
const { user } = useFetchUser();
const user_id = user && user.sub;
- NB;
- Related questions from around the internet
- auth0.com: Glossary
- auth0.com: The Ultimate Guide to Next.js Authentication with Auth0
- auth0.com: Laravel API: Authorisation
- auth0.com: The Ultimate Guide to Next.js Authentication with Auth0
- github.com: Next.js and Auth0 Example
- github.com: The next folder project is built on top this repo
- jwt.io: JWT.io