AdonisJS 6 Session Authentication in 15 Minutes
In this lesson, we'll learn how to add authentication to a new AdonisJS 6 application using the session guard. In these 15 minutes, you'll learn how to register a user, logout a user, verify a user's credentials and log them in, and more.
- Author
- Tom Gobich
- Published
- Apr 18
- Duration
- 15m 18s
Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.
Adocasts
Burlington, KY
Transcript
AdonisJS 6 Session Authentication in 15 Minutes
-
(upbeat music)
-
So first let's go ahead and get ourselves
-
a new AdonisJS 6 application
-
by doing npm init adonisjs@latest,
-
and we'll call this adonisjs6 session auth.
-
Let's run that, and then it will ask us
-
what starter kit we'd like to start with.
-
Today we're gonna be talking about this as a monolith,
-
so everything within one application.
-
In a future lesson, we'll take a look at this
-
within split applications as well.
-
So for this lesson, we'll select the web starter kit.
-
Then we're gonna use session authentication,
-
and my database driver is Postgres,
-
but do select whichever one's applicable to you here.
-
We'll have it install our dependencies.
-
Then while it's doing that,
-
let's open up our text editor,
-
go into open, find our project,
-
finds within code, then down in tutorials,
-
AdonisJS 6, session auth.
-
Let's open that up, and our output's gonna show
-
that we're missing an environment variable for db database.
-
We'll take care of that here in a second,
-
but before we do, let's dive back into our terminal
-
and make sure everything installed okay, which it did.
-
So before we start our server up,
-
let's go ahead and take care of our environment variables.
-
For the most part, everything that's here already
-
is a okay to leave as is.
-
We just need to fill out our database password.
-
Mine's the very secure password,
-
and then our database name.
-
I believe I have one called test.
-
Then let's hold command P and dive into our routes file.
-
Let's create our routes for our authentication.
-
So we'll do router.
-
And we'll put these inside of a group.
-
So we'll define a callback function,
-
and any routes that we define within this group
-
will get applied to the group.
-
So we can name this group as auth.
-
If you wanted to prefix the URL as well,
-
we could do that there too.
-
We'll leave that out for right now.
-
As for our routes itself, we'll do router.get/register.
-
And we're gonna need some controllers
-
to actually house the logic with it.
-
So let's jump back into our terminal, clear that out.
-
Let's go ahead and CD into our project.
-
So I named mine AdonisJS6, session auth.
-
There we go, clear that out once more.
-
And then we'll do node is make controller.
-
And what I like doing for my authentication controllers
-
is having one controller per action
-
that we have for authentication.
-
So we'll have one controller for registration,
-
one for login, and one for logout.
-
And that just helps keep things clean
-
as our authentication logic grows
-
throughout the life of our application.
-
And furthermore, we can put all of them
-
inside of a single directory called auth.
-
So if we just do auth/,
-
and we'll put whatever we name here.
-
So let's do register to create a register controller
-
inside of an auth directory.
-
Then for our register controller,
-
let's also create a show and a store method inside of there.
-
And let's keep our register name singular by doing -s.
-
Let's run that, and there we go.
-
While we're here, let's hit up and change register to login.
-
And let's run that, and we'll do one more for logout.
-
So let's go down to log and add out, there we go.
-
So our controller should be good, clear that out.
-
And let's go ahead and verify that our server will boot up.
-
So let's do npm run dev awesome.
-
Let's hide that away,
-
and let's dive back into our text editor.
-
So for our register route,
-
we're gonna want to use our register controller.
-
So we hit tab to auto import that.
-
And specifically for this one,
-
we're gonna wanna use the show method.
-
And then we'll name this route as register.show.
-
So the full route's name is now auth.register.show.
-
Since we've defined this route
-
inside of our auth router group.
-
Give that a save to fix all of the formatting.
-
Let's give this a quick copy.
-
Switch our get to a post.
-
This will handle our register form submission.
-
We can keep the URL the same here.
-
Switch over to our show and change that to store
-
and change our show to store there as well.
-
Okay, give that a save.
-
And let's dive into our app folder,
-
controllers, auth,
-
and let's dive into our register controller.
-
We're gonna start with our show method.
-
What we're gonna want is to extract view
-
out of our HTTP context and return view,
-
render pages slash,
-
and we'll create a view at auth slash register.
-
Give that a save, and let's go create that.
-
So resources, views, pages,
-
let's right click on pages, new file,
-
create a folder called auth.
-
And inside of that folder,
-
we'll create a file called register.edge.
-
So that we don't need to redefine all of the HTML5 markup,
-
let's right click on our views directory, new file,
-
let's create a folder called components,
-
create a folder inside of components called layout,
-
and a file inside of layout called index.edge.
-
Once we have that, let's dive into our views,
-
pages, homepage, give everything there a copy,
-
and let's paste that into our new layout file.
-
Replace the body contents with three curly braces,
-
a weight, and let's render out our main slot.
-
So we'll do slots.main there.
-
We'll also allow ourselves to define a title.
-
So we'll do two curly braces to add an interpolation there.
-
And we'll just render out a title
-
or an empty string hyphen Adonis JS6 session auth
-
to serve as our default title there.
-
Give that a save, back into our auth and our register page.
-
And we can do @layout title register,
-
end our layout, and let's add an H1 register.
-
We'll add a form with a label, a span.
-
We're gonna want a full name.
-
So we'll have an input type equals text,
-
name equals full name, value equals,
-
and we can grab the old value that was submitted
-
or the full name.
-
And if one was not previously submitted,
-
we'll default that to an empty string and end our input.
-
Then we'll display any errors with input error,
-
reach for our full name errors, and then end that.
-
And if we got any,
-
they'll be injected as dollar sign messages.
-
We just join those messages as a comma delimited list.
-
Let's give that a copy
-
'cause we're gonna need it two more times
-
and paste it in those two times.
-
Our second one is going to be our email.
-
So email there, type will be email,
-
name will be email, old will be email,
-
and our full name will be email.
-
Then we'll dive down to this one,
-
and this one will serve as our password field.
-
So we just need to switch all of these
-
to password as well.
-
Now you can extract this out into a component
-
so that you only need to define this once.
-
And our Let's Learn AdonisJS 6 series
-
covers exactly how to do that.
-
Okay, then we'll have our button type equals submit,
-
and we'll add in register for the text for that button.
-
So at present, this form would not work.
-
We haven't rigged it up to anything quite yet.
-
Let's go ahead and open up our browser,
-
head into slash register to view our registration page.
-
Not pretty, but everything looks correct.
-
Awesome.
-
Let's rig up our form real quick.
-
So this will be a method of post,
-
and an action will point to our route,
-
auto off, register, store.
-
We're also going to want this to submit with a CSRF token.
-
So we'll call the CSRF field to inject a hidden input
-
with a CSRF token into this form.
-
If we dive into our config shield
-
and scroll down to the CSRF section,
-
this is enabled by default.
-
So if we don't include that, our form will fail
-
due to that shield configuration
-
and CSRF protection being enabled.
-
Let's dive back into our terminal,
-
stop our server for a brief second, clear that out.
-
Let's create ourselves an auth validator file.
-
So do node is make validator,
-
and we'll just call this auth.
-
And then while we're here
-
and while we have our server stopped,
-
let's go ahead and also migrate our database.
-
So we'll clear that out.
-
We'll do node is migration,
-
and then you most likely will just need to call run
-
if you're working with a brand new database.
-
I, however, am reusing a database from a previous lesson.
-
So I need to call fresh,
-
which will truncate and drop all tables
-
inside of the database, regardless of what they are,
-
and re-migrate with our fresh migrations.
-
So I'm gonna run that, and there we go.
-
So it dropped all of my tables
-
and re-migrated the one migration that we have
-
to create our users table.
-
Let's clear that out, and we can boot back up our server.
-
So npm run dev, and jump back into our text editor.
-
Now we need to take care
-
of our auth register store handler.
-
So we'll jump back into our register controller.
-
We're going to need our request
-
and our response from our HTTP context.
-
Let's grab our validated data.
-
So we'll do const data equals await request,
-
validate using to use our validator,
-
and let's go create that validator.
-
So we'll jump into our auth validators,
-
export const register validator equals vine compile,
-
vine object, and provide in our full name of vine string,
-
and we'll set a max length to 100 characters.
-
For that, we'll have our email of type vine string.
-
Define that it should be an email,
-
and let's also normalize that email as well.
-
And then lastly, we'll have our password
-
of type vine string, and let's require that
-
to be at least eight characters long.
-
So we'll set a min length of eight there.
-
Okay, we'll give that a save,
-
jump back to our register controller,
-
and now we can pass that validator
-
directly into our validate using.
-
So we'll type out register validator
-
and hit tab to auto import that,
-
and provide that directly into our validate using
-
with our request.
-
This will then pluck the data off of our requests
-
and validate it against our register validator,
-
and provide us back that validated data
-
of our full name, email, and password.
-
So we can then directly use this data to create our user.
-
So we'll do const user equals await user,
-
hit tab to auto import that model,
-
dot create, and provide in that validated data.
-
Once we've done that, we just need to log the user in.
-
And we're gonna wanna use the auth module
-
provided directly from our HTTP context to do so.
-
So we'll extract that out as well.
-
We'll then await auth to use our web session guard
-
to log in the specific user that we have created.
-
Once we've logged in that user,
-
we can then return response dot redirect.
-
And we haven't given our homepage a name quite yet,
-
so we'll just do to path and provide slash
-
to go back to the homepage.
-
So there is our registration flow.
-
Let's go ahead and dive now into our homepage
-
and give us something to say
-
whether or not a user is authenticated.
-
So we can do at if auth user to determine
-
whether or not a user has been authenticated
-
for the current session.
-
And let's end that if.
-
So if we have an auth user,
-
then we'll say logged in as,
-
use string interpolation to reach for auth user,
-
and we'll display out the full name.
-
Let's give that a save.
-
And if we're not logged in,
-
so we'll add in an else here.
-
Let's display a quick link to our register page.
-
So ahref route auth register shell
-
and provide it in the text register there.
-
Let's give that a save, jump back into our browser.
-
And let's go back to our homepage real quick
-
just to verify that it only shows register here.
-
Give that a click.
-
We go into our register page.
-
I'll enter in my full name,
-
some email and some password.
-
We'll hit enter to submit that.
-
And everything appears to have worked.
-
We got redirected back to our homepage,
-
but we still just see register.
-
So we are actually authenticated,
-
but this request just doesn't know it
-
because we haven't informed it to check
-
for that authenticated user yet.
-
So let's go and do that real quick.
-
So let's hold command P and dive back into our routes.
-
And let's switch our home route
-
from using the on to router.get/
-
so that we have to find this with an actual route handler.
-
And we'll just return ctx_view_render_pages.
-
Hold, okay, let's get rid of that.
-
Then to check for authenticated user on a session,
-
we just need to await ctx_auth and call the check method.
-
This will check for an authenticated user
-
and populate our auth.user
-
with that authenticated user's details if anyone is found.
-
Otherwise it will continue onward
-
and our auth.user will just be null.
-
So we can save that.
-
And with that save,
-
let's go ahead and dive back into our browser,
-
give the page a quick refresh.
-
And there we go.
-
We see we're logged in as Tom Gubbage now.
-
Awesome.
-
So before we can cover logging in,
-
we need to be able to log back out.
-
So let's do that real quick.
-
Dive back into our text editor,
-
jump down to our routes,
-
and let's add in a new one called router.host/logout.
-
We'll have our logout controller handle this.
-
And we created this with a show in store.
-
Let's just call that one handle.
-
We'll go fix that here in a second, is logout.
-
We'll give that a save.
-
And let's go dive into that logout controller
-
and replace one of these methods with just the word handle.
-
Get rid of the other one as well.
-
Within here, we're gonna want our response
-
so that we can redirect back away.
-
And we're gonna want our auth as well.
-
So we'll just await auth.filt_to_use,
-
the web guard, and log out the current session.
-
Once we've done that, we're good to just return,
-
response, redirect, do,
-
and we'll point it to our homepage.
-
Give that a save.
-
And let's dive back into our homepage.
-
And if we are logged in, let's add in a quick form,
-
method equals post, action equals route, auth.logout.
-
Add in that CSRF field and the button of type submit
-
that says logout.
-
Give that a save.
-
And now we should be able to dive back into our browser.
-
We see that button, we can click it,
-
and voila, we're now logged out.
-
So lastly, we just need to add in our login flow.
-
So let's start with our routes once more.
-
So we'll dive back into our routes file.
-
Let's give both of our register routes here
-
a copy and a paste.
-
Click in the middle of our register word,
-
option command down to get double cursors,
-
option right arrow to jump to the end of the word,
-
and option shift left arrow to highlight
-
and select the whole word for both lines.
-
Switch that to login,
-
option right arrow two more times,
-
option shift left arrow to select the entire
-
ready to give register controller.
-
And let's switch that to login controller
-
and hit tab to import that.
-
Option right arrow a few more times
-
to jump over to register,
-
and option shift right arrow
-
to select the entire word register.
-
And let's replace that with login.
-
Okay, give that a save,
-
scroll up and dive into our login controller,
-
show, we're just going to want view,
-
and we'll return view, render pages,
-
auth, login, and let's create that page real quick.
-
So we'll dive down to auth,
-
right click register, give that a copy,
-
right click auth, give it a paste.
-
With register copy selected,
-
we'll switch that to login.edge.
-
Do a quick find and replace for the word register
-
to login, we'll tell it to be case sensitive
-
with that search, and replace all finds.
-
Okay, then we'll scroll up.
-
We won't need our full name for logging in,
-
so we'll get rid of that.
-
We'll replace our auth.register.store
-
with auth.login.store,
-
and we should be good to go with our form now.
-
Give that a save, jump back up to our login controller,
-
and let's take care of our store method.
-
So we're going to want our request, response,
-
and auth out of our HTTP context.
-
And first we're going to want to grab that validated data.
-
So let's go back into our auth validator
-
and define one for logging in.
-
So we'll export const login validator
-
equals vine.compile, vine.object,
-
and I'm just going to go up and copy our email
-
and password, paste it in here,
-
and we'll get rid of the min length requirement
-
for our password,
-
since we aren't actually going to be storing it,
-
but just using it to check.
-
So we'll make that one a little bit more fluid there.
-
We do still want our email to be normalized though,
-
so that that check is the same as what they registered with.
-
So with that in place,
-
let's jump back into our login controller,
-
and let's get our validated data.
-
So we'll do const data equals await request,
-
validate using, and we'll use our login validator.
-
The next thing that we're going to want to do
-
is get the user matching the email
-
and the password that was provided in.
-
And if we take a look at our model for our user,
-
we're going to see this auth finder added in here by default
-
with the UID set to email
-
and the password column name set to password.
-
This auth finder is then composed into our user model,
-
and it's going to add in a couple of things for us,
-
like being able to verify our user's credentials
-
against what they've provided within their login form,
-
as well as hashing the user's password
-
whenever we actually store them into the database.
-
So during our registration step,
-
we didn't take care of that.
-
It just automatically happened for us
-
thanks to this auth finder.
-
Let's dive back into our login controller,
-
and let's use our auth model to determine
-
whether or not the user has provided a valid email
-
and password via our validated data
-
from our login validator
-
to get back the matched user for those credentials.
-
So we can do const user equals await,
-
import our user model,
-
and call verify credentials,
-
passing in the UID and the password.
-
So we can extract that out of our data
-
as email and password there.
-
And our email is our UID,
-
and our password is the password.
-
If these credentials are correct,
-
we will get back a user.
-
If they're incorrect,
-
this will then throw an error,
-
preventing further execution of our code.
-
Okay, then just like with our registration,
-
once we have access to that user,
-
we just want to await auth.use our web guard
-
to log in that user.
-
Once they're logged in,
-
we can return, response, redirect them
-
to our homepage path.
-
Give that a save.
-
Okay, lastly, let's dive back into our homepage
-
and give ourselves a link to our login page.
-
So we just copy our register link,
-
paste it in, switch that to login,
-
and switch this to login as well.
-
Give that a save,
-
and now we can jump back into our browser
-
and give everything a test.
-
So let's go to login.
-
We created a user with an email, [email protected],
-
and let's enter in that password, run that,
-
and now we're logged in as Tom Gobich.
-
We can log out.
-
Let's go back to login.
-
If we enter in [email protected],
-
which does not exist inside of our database,
-
and some password, we run that.
-
We get redirected right back here.
-
Our email is auto-populated,
-
thanks to that old check.
-
And if we wanted to,
-
we could display an error here as well.
-
So if we dive back into here,
-
let's go into our login page,
-
and let's add in a quick inspect call
-
to flashmessages.all to see what we're getting back.
-
Okay, [email protected], some password, run that.
-
There's our old email value,
-
and we can see that errors bag now has
-
E invalid credentials,
-
specifying invalid user credentials.
-
Okay, so we can dive back into our form here,
-
and up above our input fields,
-
we'll do @error,
-
and we just need to provide the key of the error
-
from the errors bag here,
-
which is E invalid credentials.
-
So we can give that a copy,
-
jump back into here,
-
and provide that into this errors tag.
-
We can end that,
-
and now if this error tag finds an error
-
called E invalid credentials,
-
it will provide that particular error via message.
-
So we could display that just like so.
-
Let's give that a save,
-
jump back into our browser,
-
give this one more test.
-
So [email protected], some invalid email there,
-
run that, and there we go.
-
So now we got invalid user credentials.
-
We keep the old email,
-
and the password is omitted from the old automatically
-
as a measure of safety.
-
1.0AdonisJS Authentication in 15 Minutes16m 7s
-
2.0How To Do Multi Model Authentication with AdonisJS and Lucid ORM18m 29s
-
3.0AdonisJS User Role Authentication in 15 Minutes15m 43s
-
4.0AdonisJS 5 API & Nuxt 3 SSR Authentication in 15 Minutes13m 58s
-
5.0AdonisJS 6 Session Authentication in 15 Minutes15m 18s
-
6.0AdonisJS 6 Access Token Authentication in 20 Minutes20m 8s
-
7.0Three Approaches for Organizing your AdonisJS Business Logic Operations28m 8s
-
8.0How To Make A Simple AdonisJS 6 Package25m 24s
-
How To Add Social Authentication with AdonisJS Ally & Google25m 30s
Join The Discussion! (14 Comments)
Please sign in or sign up for free to join in on the dicussion.
mark-gidman
This is a great walkthrough on session based auth. One of the things I'm struggling with is using Ally to authenticate via Google and then convert that into an authorized session. I have the Google auth flow working as expected. I just don't know how to use that data to flow into a user session. Have you seen any resources on this?
Please sign in or sign up for free to reply
tomgobich
Thank you, Mark!! We haven't discussed social authentication with AdonisJS 6 in any of our lessons quite yet. But, once you get the user details from Google, you'll want to determine if you have a matching user already in your database, and if not, create that user. Once you've either found the matching user or created the new user, you can log them in using AdonisJS Auth.
Hope this helps!
Please sign in or sign up for free to reply
alan-pena
Hello, first thing first thanks for all the work you do for the AdonisJS community.
I have an issue with part "How to perform user registration", it create my user into my database but it doesn't login my user. I checked the code from the github repo and there I can't found any diff.
Any idea ?
Please sign in or sign up for free to reply
tomgobich
Hi Alan! Thank you for watching!
Are you able to share a link to the repository or a reproduction of it? Based on the two snippets, all looks good, but the issue could be in the user model, config, or middleware.
Please sign in or sign up for free to reply
alan-pena
Hi,
I cannot share my project in a repo because it's a enterprise project but i can try to share some screenshots (due to max characters in comments)
Here's a Imgur post with all the screenshots : https://imgur.com/a/rYIUWJx
All the other stuff like auth middlewares are the default ones.
Please sign in or sign up for free to reply
tomgobich
It might be something related to the UUID implementation. I haven't personally used them in AdonisJS so I'm not overly familiar with everything needed to get them working, but I do believe you need to inform AdonisJS you're self assigning the primary key via the model.
You can try digging into the
node_modules
to console log/breakpoint where AdonisJS is attempting to authenticate the user to see if:Is it finding the
userId
from the session okayIs it able to find the user via that id
Additionally, though this wouldn't cause your issue, both the
auth
middleware and theauth.check()
method calls theauth.authenticate()
method, so you only need one or the other.The auth middleware is great when you need the user to be authenticated to access the route. It will throw an error if the user is not authenticated.
The
auth.check()
call is great when the user can be authenticated but doesn't need to be to access the route.Hope this helps!
Please sign in or sign up for free to reply
alan-pena
Indeed it was this line of code
static selfAssignPrimaryKey = true
that was missing, now it work properly!Thanks a lot for your time!
Please sign in or sign up for free to reply
tomgobich
Awesome, I'm glad to hear everything is working a-okay now! 😊
Anytime, happy to help!
Please sign in or sign up for free to reply
aspinazze
When it comes to setting up authentication with Adonis but in the case of only using Adonis as your BE I am assuming it's the same steps but skipping the Inertia steps such as the GETs for the views? Also out of curiosity if you are using a client-side react app with Session Guard I am assuming you are doing no validation in terms of client-side validation for auth but instead relying on the response back from Adonis via an API call and letting the server manage the auth session?
Please sign in or sign up for free to reply
tomgobich
Yeah, if you're using session authentication using AdonisJS as an API, so long as the two apps share the same domain, the approach would still be the same just without the front-end routes & pages. Depending on your set up you may need to update the session cookie's domain config to allow subdomains.
In terms of validation, that's up to your discretion. You'll always want to perform server-side validation for data integrity's sake since client-side validation can be easily circumvented. Client-side validation is generally provided to improve user experience since it is a quicker way to provide feedback. When working with reactive client-side frameworks, it is generally considered good practice to validate on both the client & server side.
Please sign in or sign up for free to reply
aspinazze
Awesome thanks for your explanation 😀
Please sign in or sign up for free to reply
tomgobich
Anytime!! 😊
Please sign in or sign up for free to reply
aspinazze
One other question I thought about as well is CSRF token validation specific to Inertia/server-side rendered frameworks? I haven't come across that before while working mostly client side while submitting data.
Please sign in or sign up for free to reply
tomgobich
No, CSRF is a broad reaching vulnerability across the web and is not specific to inertia nor server-side rendered applications. The goal of these attacks is to hijack a state changing request, causing an authenticated user to perform an action they did not want nor intend to happen.
You can go more in-depth on CSRF by reading through OWASP's guide.
Please sign in or sign up for free to reply