Skip to main content
IHP provides a complete authentication system with session management, secure password hashing, and optional email confirmation.

Quick Start: HTTP Basic Auth

For simple use cases, IHP provides HTTP Basic Authentication:
instance Controller WidgetsController where
    beforeAction = basicAuth "sanja" "hunter2" "myapp"
The parameters are: username, password, and authentication realm.

Full Authentication Setup

There’s an IHP Casts Episode covering this documentation.

Database Schema

Your users table needs these fields:
CREATE TABLE users (
    id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
    email TEXT NOT NULL,
    password_hash TEXT NOT NULL,
    locked_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,
    failed_login_attempts INT DEFAULT 0 NOT NULL
);
Passwords are stored as salted hashes using pwstore-fast. Users are locked for one hour after 10 failed login attempts.

Configuration Steps

1

Update Web/Types.hs

Add authentication types:
import IHP.LoginSupport.Types

instance HasNewSessionUrl User where
    newSessionUrl _ = "/NewSession"

type instance CurrentUserRecord = User

data SessionsController
    = NewSessionAction
    | CreateSessionAction
    | DeleteSessionAction
    deriving (Eq, Show, Data)
2

Set up routing in Web/Routes.hs

instance AutoRoute SessionsController
3

Create Web/Controller/Sessions.hs

module Web.Controller.Sessions where

import Web.Controller.Prelude
import Web.View.Sessions.New
import qualified IHP.AuthSupport.Controller.Sessions as Sessions

instance Controller SessionsController where
    action NewSessionAction = Sessions.newSessionAction @User
    action CreateSessionAction = Sessions.createSessionAction @User
    action DeleteSessionAction = Sessions.deleteSessionAction @User

instance Sessions.SessionsControllerConfig User
4

Create login view at Web/View/Sessions/New.hs

module Web.View.Sessions.New where
import Web.View.Prelude
import IHP.AuthSupport.View.Sessions.New

instance View (NewView User) where
    html NewView { .. } = [hsx|
        <div class="h-100" id="sessions-new">
            <div class="d-flex align-items-center">
                <div class="w-100">
                    <div style="max-width: 400px" class="mx-auto mb-5">
                        <h5>Please login</h5>
                        {renderForm user}
                    </div>
                </div>
            </div>
        </div>
    |]

renderForm :: User -> Html
renderForm user = [hsx|
    <form method="POST" action={CreateSessionAction}>
        <div class="mb-3">
            <input name="email" value={user.email} type="email" 
                   class="form-control" placeholder="E-Mail" 
                   required="required" autofocus="autofocus" />
        </div>
        <div class="mb-3">
            <input name="password" type="password" 
                   class="form-control" placeholder="Password"/>
        </div>
        <button type="submit" class="btn btn-primary w-100">Login</button>
    </form>
|]
5

Update Web/FrontController.hs

Add imports and mount the session controller:
import IHP.LoginSupport.Middleware
import Web.Controller.Sessions

instance FrontController WebApplication where
    controllers =
        [ startPage WelcomeAction
        , parseRoute @SessionsController
        -- Generator Marker
        ]

instance InitControllerContext WebApplication where
    initContext = do
        setLayout defaultLayout
        initAuthentication @User

Using Authentication

Accessing the Current User

In your controllers:
action MyAction = do
    let text = "Hello " <> currentUser.email
    renderPlain text
Accessing currentUser when logged out throws an exception and redirects to the login page.
For optional authentication:
action MyAction = do
    case currentUserOrNothing of
        Just currentUser -> renderPlain ("Hello " <> currentUser.email)
        Nothing -> renderPlain "Please login first"
In your views:
[hsx|<h1>Hello {currentUser.email}</h1>|]

Creating Users

Generate a Users controller and implement password hashing:
action CreateUserAction = do
    let user = newRecord @User
    let passwordConfirmation = param @Text "passwordConfirmation"
    user
        |> fill @["email", "passwordHash"]
        |> validateField #passwordHash (isEqual passwordConfirmation |> withCustomErrorMessage "Passwords don't match")
        |> validateField #passwordHash nonEmpty
        |> validateField #email isEmail
        |> validateIsUnique #email
        >>= ifValid \case
            Left user -> render NewView { .. }
            Right user -> do
                hashed <- hashPassword user.passwordHash
                user <- user
                    |> set #passwordHash hashed
                    |> createRecord
                setSuccessMessage "You have registered successfully"
                redirectToPath "/"

Performing Actions on Login

Use beforeLogin to run code after authentication but before redirect:
instance Sessions.SessionsControllerConfig User where
    beforeLogin user = do
        user
            |> modify #logins (\count -> count + 1)
            |> updateRecord
        pure ()
To block unconfirmed users:
instance Sessions.SessionsControllerConfig User where
    beforeLogin user = do
        unless user.isConfirmed do
            setErrorMessage "Please confirm your email first"
            redirectTo NewSessionAction

Logout

Add a logout link:
<a class="js-delete js-delete-no-confirm" href={DeleteSessionAction}>Logout</a>

Email Confirmation

Email confirmation requires IHP Pro.
Add these columns to your users table:
CREATE TABLE users (
    /* ... existing columns ... */
    confirmation_token TEXT DEFAULT NULL,
    is_confirmed BOOLEAN DEFAULT false NOT NULL
);
1

Add confirmation action to Web/Types.hs

data UsersController
    = NewUserAction
    | ConfirmUserAction { userId :: !(Id User), confirmationToken :: !Text }
    deriving (Eq, Show, Data)
2

Implement confirmation in Web/Controller/Users.hs

import qualified IHP.AuthSupport.Controller.Confirmations as Confirmations

action ConfirmUserAction { userId, confirmationToken } = 
    Confirmations.confirmAction userId confirmationToken

instance Confirmations.ConfirmationsControllerConfig User where
3

Create confirmation email at Web/Mail/Users/ConfirmationMail.hs

module Web.Mail.Users.ConfirmationMail where
import Web.View.Prelude
import IHP.MailPrelude
import IHP.AuthSupport.Confirm

instance BuildMail (ConfirmationMail User) where
    subject = "Confirm your Account"
    to ConfirmationMail { .. } = Address { addressName = Nothing, addressEmail = user.email }
    from = "[email protected]"
    html ConfirmationMail { .. } = [hsx|
        Hey,
        just checking it's you.

        <a href={urlTo (ConfirmUserAction user.id confirmationToken)} target="_blank">
            Activate your Account
        </a>
    |]
4

Send confirmation email on registration

import IHP.AuthSupport.Confirm
import Web.Mail.Users.ConfirmationMail

action CreateUserAction = do
    -- ... create user ...
    sendConfirmationMail user
    setSuccessMessage "Please check your email for a confirmation link"
    redirectTo NewSessionAction
5

Block unconfirmed logins in Web/Controller/Sessions.hs

import qualified IHP.AuthSupport.Confirm as Confirm

instance Sessions.SessionsControllerConfig User where
    beforeLogin user = Confirm.ensureIsConfirmed user

Command Line Tools

Hash a Password

For manual database inserts:
$ hash-password
Enter your password and press enter:
hunter2
sha256|17|Y32Ga1uke5CisJvVp6p2sg==|TSDuEs1+Xdaels6TYCkyCgIBHxWA/US7bvBlK0vHzvc=
Or use hashPassword in your application code.

Build docs developers (and LLMs) love