Authentication
Formidable provides a starter authentication system for both session and jwt based applications. By default, session based authentication is enabled.
The session based authentication system enables the use of cookies, and stores the session data in memory, file or redis. While the jwt based authentication system enables the use of JWT tokens, and stores authentication data in the database.
You can find your application's authentication configuration in config/auth.imba or config/auth.ts.
Getting Started
Formidable will automatically enable authentication for you. Should you not want to use authentication in your application, you can disable it by removing Auth.routes! from the app/Resolvers/RouterServiceResolver.imba or by removing Auth.routes() from the app/Resolvers/RouterServiceResolver.ts, this will disable all authentication routes.
Database Considerations
Its important to note that you will need to create a database for your application to use authentication. If a database has been created, head over to your .env file and config/database.imba or config/database.ts config file to configure your database connection, once this is done, you can run:
node craftsman migrate:latest
This will create all the tables needed for authentication. users, password_resets and personal_access_tokens tables will be created.
Configure Client
The email and password routes require a client url to be configured. This url is prepended to the routes. To configure this url, head over to your .env and set the CLIENT_URL environment variable.
Actions
Login
Log a user in
- API
- Formidable Views
- Imba Components
POST http://127.0.0.1:3000/login
Content-Type: application/json
{
"email": "email-address",
"password": "password"
}
Create a Login view:
import { config } from '@formidablejs/framework/lib/Support/Helpers'
import { URL } from '@formidablejs/framework'
import { View } from '@formidablejs/framework'
export class Login < View
def render
<html>
<head>
<title> "Login - {config('app.name')}"
<body>
<h1> "Login"
<form action=URL.route('login') method="POST">
<input type="hidden" name="_token" value=get('csrf_token')>
<div>
<label> "Email address"
<input type="email" name="email" value=old('email')>
if hasError('email')
for error in error('email')
<p> error
<div>
<label> "Password"
<input type="password" name="password">
if hasError('password')
for error in error('password')
<p> error
<div>
<button> "Login"
Redirect users after a successful login using the onAuthenticated auth event:
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { Redirect } from '@formidablejs/framework'
import { Request } from '@formidablejs/framework'
...
export class AppServiceResolver < ServiceResolver
def boot
...
# Redirect the user to the home route after logging in.
Auth.onAuthenticated do(request\Request)
Redirect.to('/home') if request.expectsHtml!
import { useForm } from '@formidablejs/view'
export tag Login
user = useForm({
email: ''
password: ''
remember: false
})
def login
user.post('/login').then do
window.location.assign('/home') # authenticated route
def render
<self>
<form @submit.prevent=login>
<div>
<label> "Email address"
<input type="email" name="email" value=user.email disabled=user.processing?>
if user.errors.email
for error in user.errors.email
<p> error
<div>
<label> "Password"
<input type="password" name="password" value=user.password disabled=user.processing?>
if user.errors.password
for error in user.errors.password
<p> error
<div>
<label>
<input type="checkbox" checked=user.remember disabled=user.processing?>
<span> "Remember me"
<div>
<button disabled=user.processing?> "Login"
Logout
Log a user out
- API
- Formidable Views
- Imba Components
POST http://127.0.0.1:3000/logout
Add the following code to your home view:
import { URL } from '@formidablejs/framework'
import { View } from '@formidablejs/framework'
export class Home < View
def render
<html>
<head>
...
<body>
...
<a href=URL.route('logout') html:onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
"Logout"
<form#logout-form[d:none] action=URL.route('logout') method="POST">
<input type="hidden" name="_token" value=get('csrf_token')>
Redirect users after a successful logout using the onSessionDestroyed auth event:
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { Redirect } from '@formidablejs/framework'
import { Request } from '@formidablejs/framework'
...
export class AppServiceResolver < ServiceResolver
def boot
...
# Redirect the user to the login route after logging out.
Auth.onSessionDestroyed do(request\Request)
Redirect.to('/login') if request.expectsHtml!
import { useForm } from '@formidablejs/view'
export tag Login
def logout
useForm!.post('/logout').then do
window.location.assign('/login')
def render
<self>
...
<a href="/logout" @click.prevent=logout> "Logout"
Register
Register a user
- API
- Formidable Views
- Imba Components
POST http://127.0.0.1:3000/register
Content-Type: application/json
{
"name": "full-name",
"email": "email-address",
"password": "password",
"password_confirmation": "password"
}
Create a Register view:
import { config } from '@formidablejs/framework/lib/Support/Helpers'
import { URL } from '@formidablejs/framework'
import { View } from '@formidablejs/framework'
export class Register < View
def render
<html>
<head>
<title> "Register - {config('app.name')}"
<body>
<h1> "Register"
<form action=URL.route('register') method="POST">
<input type="hidden" name="_token" value=get('csrf_token')>
<div>
<label> "Name"
<input type="text" name="name" value=old('name')>
if hasError('name')
for error in error('name')
<p> error
<div>
<label> "Email address"
<input type="email" name="email" value=old('email')>
if hasError('email')
for error in error('email')
<p> error
<div>
<label> "Password"
<input type="password" name="password">
if hasError('password')
for error in error('password')
<p> error
<div>
<label> "Confirm Password"
<input type="password" name="password_confirmation">
if hasError('password_confirmation')
for error in error('password_confirmation')
<p> error
<div>
<button> "Register"
Redirect users after a successful registration using the onRegistered auth event:
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { Redirect } from '@formidablejs/framework'
import { Request } from '@formidablejs/framework'
...
export class AppServiceResolver < ServiceResolver
def boot
...
# Redirect the user to the home route after registration.
Auth.onRegistered do(request\Request)
Redirect.to('/home') if request.expectsHtml!
import { useForm } from '@formidablejs/view'
export tag Register
user = useForm({
name: ''
email: ''
password: ''
password_confirmation: ''
})
def register
user.post('/register').then do
window.location.assign('/home') # authenticated route
def render
<self>
<form @submit.prevent=register>
<div>
<label> "Name"
<input type="text" name="name" value=user.name disabled=user.processing?>
if user.errors.name
for error in user.errors.name
<p> error
<div>
<label> "Email address"
<input type="email" name="email" value=user.email disabled=user.processing?>
if user.errors.email
for error in user.errors.email
<p> error
<div>
<label> "Password"
<input type="password" name="password" value=user.password disabled=user.processing?>
if user.errors.password
for error in user.errors.password
<p> error
<div>
<label> "Confirm Password"
<input type="password" name="password_confirmation" value=user.password_confirmation disabled=user.processing?>
if user.errors.password_confirmation
for error in user.errors.password_confirmation
<p> error
<div>
<button disabled=user.processing?> "Register"
Email Verification
Verify a user's email address
- API
- Formidable Views
- Imba Components
POST http://127.0.0.1:3000/email/verify?email={email-address}&signature={signature}
The email address and signiture are provided in the query string and can be found in the VerifyEmail mailer that get's sent to the user. When the user clicks on the link in the email, the user will be redirected to "CLIENT_URL/email/verify?email=email-address&signature=signature".
Here, you can extract the email address and signature from the query string and verify the email address by sending a post request to "/email/verify" with the extracted email address and signature.
Create a Email Verify view:
import { config } from '@formidablejs/framework/lib/Support/Helpers'
import { View } from '@formidablejs/framework'
import { URL } from '@formidablejs/framework'
export class EmailVerify < View
def render
const verifyUrl = URL.route('email.verify', {
email: get('email'),
signature: get('signature')
})
<html>
<head>
<title> "Verify - {config('app.name')}"
<body>
if hasSession('message')
<p> session('message')
else
<a href=verifyUrl html:onclick="event.preventDefault(); document.getElementById('verify-form').submit();">
"Verify Email"
<form#verify-form[d:none] action=verifyUrl method="POST">
<input type="hidden" name="_token" value=get('csrf_token')>
Redirect users back after email verification using the onEmailVerified auth event:
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { Request } from '@formidablejs/framework'
import { FastifyReply } from '@formidablejs/framework'
import { Redirect } from '@formidablejs/framework'
...
export class AppServiceResolver < ServiceResolver
def boot
...
# Redirect the user to the home route after logging in.
Auth.onEmailVerified do(request\Request, reply\FastifyReply, verified\boolean)
Redirect.back!.with('message', verified ? 'success' : 'failed') if request.expectsHtml!
import { useForm } from '@formidablejs/view'
export tag EmailVerify
user = useForm!
def verify
user.post(router.realpath).then do
window.location.assign('/home') # authenticated route
def render
<self>
if user.isFatal? || user.errors.email
<p> "Email could not be verified"
else
<form @submit.prevent=verify>
<div>
<button disabled=user.processing?> "Verify email"
Resend Mail
Resend an email verification email to a user
POST http://127.0.0.1:3000/email/resend
Content-Type: application/json
{
"email": "email-address"
}
Forgot Password
Request a password reset email
POST http://127.0.0.1:3000/password/forgot
Content-Type: application/json
{
"email": "email-address"
}
Reset Password
Reset a password
POST http://127.0.0.1:3000/password/reset
Content-Type: application/json
{
"password": "password",
"password_confirmation": "password",
}
The email address and signiture are provided in the query string and can be found in the ForgotPassword mailer that get's sent to the user. When the user clicks the link in the email, the user will be redirected to {CLIENT_URL}/password/reset?email={email-address}&signature={signature}.
Here, you can extract the email address and signature from the query string and reset the user's password by sending a post request to /password/reset with the extracted email address and signature.
Authentication Events
Formidable provides a number of events that can be used to hook into your application's authentication.
beforeLogin
The beforeLogin event is fired before a user is logged in:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.beforeLogin do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.beforeLogin((request, reply) => {
// Do something
})
}
}
beforeLogout
The beforeLogout event is fired before a user is logged out:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.beforeLogout do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.beforeLogout((request, reply) => {
// Do something
})
}
}
beforeRegister
The beforeRegister event is fired before a user is registered:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.beforeRegister do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.beforeRegister((request, reply) => {
// Do something
})
}
}
beforeVerify
The beforeVerify event is fired before a user email is verified:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.beforeVerify do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.beforeVerify((request, reply) => {
// Do something
})
}
}
beforeResend
The beforeResend event is fired before a user verification email is resent:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.beforeResend do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.beforeResend((request, reply) => {
// Do something
})
}
}
beforeForgot
The beforeForgot event is fired before a user password reset email is sent:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.beforeForgot do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.beforeForgot((request, reply) => {
// Do something
})
}
}
beforeReset
The beforeReset event is fired before a user password is reset:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.beforeReset do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.beforeReset((request, reply) => {
// Do something
})
}
}
onAuthenticated
The onAuthenticated event is fired after a user is logged in:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.onAuthenticated do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.onAuthenticated((request, reply) => {
// Do something
})
}
}
onRegistered
The onRegistered event is fired after a user is registered:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.onRegistered do(request, reply)
# Do something
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.onRegistered((request, reply) => {
// Do something
})
}
}
Custom Authentication Handlers
Formidable provides an easy way to write your own authentication handlers.
onLogin
The onLogin hook is used to handle the login process:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.onLogin do(request, reply)
# Log the user in
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.onLogin((request, reply) => {
// Log the user in
})
}
}
onRegister
The onRegister hook is used to handle the registration process:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.onRegister do(request, reply)
# Register the user
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.onRegister((request, reply) => {
// Register the user
})
}
}
onForgot
The onForgot hook is used to handle the forgot password process:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.onForgot do(request, reply)
# Send the user a password reset email
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.onForgot((request, reply) => {
// Send the user a password reset email
})
}
}
onReset
The onReset hook is used to handle the password reset process:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.onReset do(request, reply)
# Reset the user's password
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.onReset((request, reply) => {
// Reset the user's password
})
}
}
onVerification
The onVerification hook is used to handle the email verification process:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.onVerification do(request, reply)
# Verify the user's email address
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.onVerification((request, reply) => {
// Verify the user's email address
})
}
}
onEmailResend
The onEmailResend hook is used to handle the email verification resend process:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver < ServiceResolver
def boot
Auth.onEmailResend do(request, reply)
# Resend the user's email verification email
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth.onEmailResend((request, reply) => {
// Resend the user's email verification email
})
}
}
Authentication Mailers
By default, Formidable provides VerifyEmail and ResetPassword mailers that can be used to send email verification and password reset emails to your users:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { VerifyEmail } from '../Mail/VerifyEmail'
import { ResetPassword } from '../Mail/ResetPassword'
export class AppServiceResolver < ServiceResolver
def boot
Auth
.verificationMailer(VerifyEmail)
.resetPasswordMailer(ResetPassword)
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { VerifyEmail } from '../Mail/VerifyEmail'
import { ResetPassword } from '../Mail/ResetPassword'
export class AppServiceResolver extends ServiceResolver {
boot(): void {
Auth
.verificationMailer(VerifyEmail)
.resetPasswordMailer(ResetPassword)
}
}
Protecting Routes
Formidable provides an auth middleware that can be used to require users to be logged in before accessing a route:
- Imba
- TypeScript
Route.get('ping', do 'pong').middleware(['auth'])
Route.get('ping', () => 'pong').middleware(['auth'])
JWT
If you want to use JWT tokens for authentication, you can use the jwt middleware:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { Route } from '@formidablejs/framework'
export class RouterServiceResolver < ServiceResolver
def boot
Route.group { middleware: 'jwt' }, do
Auth.routes!
require '../../routes/api'
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { Route } from '@formidablejs/framework'
export class RouterServiceResolver extends ServiceResolver {
boot(): void {
Route.group({ middleware: 'jwt' }, () => {
Auth.routes()
require('../../routes/api')
})
}
}
When logging your users in, a JWT token will be returned in the response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjQ5YTNkZWY0MDkxMzFjNThjNGY3NzYwNWU2NjNmYmRmIiwiaWF0IjoxNjM4ODA0NzgyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAifQ.A008sYS3973q-6uH2cQbgPf4Xq-v93UCvNLql0knIJ8",
"type": "Bearer",
"user": {
"name": "Donald",
"email": "donaldpakkies@gmail.com",
"email_verified_at": null,
"created_at": "2021-12-06T15:41:48.000Z",
"updated_at": "2021-12-06T15:41:48.000Z"
}
}
Now, to access /ping you can pass the JWT token in the Authorization header as a Bearer token:
POST http://127.0.0.1:3000/ping
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjQ5YTNkZWY0MDkxMzFjNThjNGY3NzYwNWU2NjNmYmRmIiwiaWF0IjoxNjM4ODA0NzgyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAifQ.A008sYS3973q-6uH2cQbgPf4Xq-v93UCvNLql0knIJ8
Session
If you want to use sessions for authentication, you can use the session middleware:
- Imba
- TypeScript
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { Route } from '@formidablejs/framework'
export class RouterServiceResolver < ServiceResolver
def boot
Route.group { middleware: 'session' }, do
Auth.routes!
require '../../routes/api'
import { AuthService as Auth } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'
import { Route } from '@formidablejs/framework'
export class RouterServiceResolver extends ServiceResolver
boot(): void {
Route.group({ middleware: 'session' }, () => {
Auth.routes()
require('../../routes/api')
})
}
}
When logging your users in, a new session will be created and a cookie will be set in the response. You may need to set a X-CSRF-TOKEN header in your authentication routes if you have CSRF protection enabled:
POST http://127.0.0.1:3000/login
X-CSRF-TOKEN: qpCuH2vmjhxgcDMkVnzfdV4tsdr9InVBgZzPTOw6Rt3YG8Hz-t1WbthldpuBOV3hrtUGMihiTraU
Content-Type: application/json
{
"email": "email-address"
"password": "password"
}
See CSRF Protection for more information.