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.