The authentication in GNU Artanis

Authentication is the process of verifying the identity of a user or system. In web applications, authentication is typically done through login forms, where users provide their credentials (such as username and password) to access protected resources.

GNU Artanis provides a flexible and extensible authentication system that allows developers to implement various authentication methods to help you handle various scenarios.

Key code

In GNU Artanis, the key code for authentication is the `#:auth` keyword argument in the route definition. This argument specifies the authentication method to be used for the route.

(if (or (:session rc 'check) (:auth rc))
    (process-request-here ...)
    (redirect-to rc "/login"))

This is a short hand for checking if the user is authenticated. Or you can do it like this:

(if (:session rc 'check) ; check if the user is logged in with a session
    (process-reqeust-here ...)
    (if (:auth rc) ; check the user/password in the request
        (process-request-here ...)
        (redirect-to rc "/login"))) ; redirect to login page if not authenticated

OK, now you may ask, but how can I specify the authentication method? You will use the #:auth shortcut to do it.

HMAC algorithm

The default HMAC is SHA256. You can specify it or customize it, but it's better to leave it to GNU Artanis. You may read the manual about HMAC.

Basic Authentication

The most common scenario is to authenticate users using a username and password in your defined DB table. Here is an example of how to set up a basic authentication route in GNU Artanis:

  • assume you have a tabled named user in DB with fields "name" and "password".
  • say you want to login at "/dashboard/login" and post the login form to "/auth".

So here the login form in app/views:

<form id="login" action="/auth" method="POST">
    <p>user name: <input type="text" name="name"></p>
    <p>password : <input type="password" name="password"></p>
    <input type="submit" value="Submit">
</form>

And the authentication code in app/controller:

(post "/auth"
  #:auth '(table user "name" "password")
  #:session #t ; you may need session to keep the login state
  (lambda (rc)
    (if (or (:session rc 'check) (:auth rc))
          (let ((referer (get-referer rc #:except "/dashboard/login*")))
            (if referer
                (redirect-to rc referer)
                (redirect-to rc "/dashboard")))
          (redirect-to rc "/dashboard/login?failed=true"))))

The password is auto salted

For product environment, you should never store the password in plain text in your DB. You should use a hashing algorithm to hash the password before storing it in the DB. GNU Artanis provides a built-in function to hash the password using HMAC with SHA256 by default. So you should make sure there is a field named "salt" in your user table.

In our example above, you should make sure the table user looks like this in DB:

CREATE TABLE user (
    id SERIAL PRIMARY KEY,
    name VARCHAR(128) NOT NULL,
    password VARCHAR(256) NOT NULL,
    salt VARCHAR(8) NOT NULL
);

Or alternatively, you can use the `create-artanis-model` macro to create the user model:

(create-artanis-model
 user
 (:deps)
 (id auto (#:primary-key))
 (name char-field (#:not-null #:maxlen 128))
 (password char-field (#:not-null #:maxlen 256)) ; hashed
 (salt char-field (#:not-null #:maxlen 8))
 ;; Add whatever fields you want here
 )

Of course, if you want to customize the salt field name, you can do it like this:

#:auth '(table user "name" "password" "salt")

Enhanced Authentication

The basic authentication assumes the existence of the table you should have in DB. This is too naive but works for most simple cases. We have an enhanced authentication mechanism that allows you to define your own authentication logic:

(define (login-checker username password)
  ;; Of course, you should check the username and password in your DB or other credentials here.
  (and (string=? username "nala")
       (string=? password "123")))

(get "/auth"
  #:auth `(post "user_field" "pw_field" ,login-checker)
  #:session #t ; you may need session to keep the login state
  (lambda (rc)
    (if (or (:session rc 'check) (:auth rc))
        (redirect-to rc "/dashboard")
        (redirect-to rc "/login?failed=true"))))

The API is #:auth ('post user-field-name pw-field-name checker-function), where:

  • user-field-name: the name of the username field in the login form.
  • pw-field-name: the name of the password field in the login form.
  • checker-function: a function that takes two arguments (username and password) and returns true if the credentials are valid, false otherwise.
    • username and password are the values submitted in the login form according to the specified field names.

The route that requires authentication

We use the #:with-auth keyword argument to specify the route that requires authentication. If the user is not authenticated, they will be redirected to the specified login page.

Please notice that #:with-auth will do all the authentication checking for you, so you don't need to check it with :with-auth since there's no such a function.

#:with-auth must be used together with #:session #t otherwise it throws an exception.

(get "/dashboard"
  #:with-auth "/login" ; if not authenticated, redirect to /login, of course you can customize it.
  (lambda (rc)
    (view-render "dashboard" (the-environment))))

Here're the possible values for #:with-auth:

  • #t: redirect to "/login" by default.
  • "string": redirect to the specified URL, say #:with-auth "/login".
  • 'status: for WebAPI, your handler may want to return HTTP status.
  • (handler rc): a custom function that takes the request context as an argument and performs custom logic, such as logging or redirecting to a different URL, whatever you like.
  • (token header checker): check the authentication token in the request headers. If the token is valid, proceed with the request; otherwise, redirect to the login page. The header is the name of the header field that contains the token, and checker is an optional function that takes the token as an argument and returns true if the token is valid, false otherwise. This is mainly for API authentication.
  • ('custom checker custom-failed custom-method): usually you don't need it.

API key authentication

You can use the token method to authenticate API requests using an API key. Here is an example:

Regular API key check

Usually, the standard key in API authentication is defined in the header Authorization, so we can use it like this:

(import (artanis artanis)
        (ice-9 match))

(define (checker token)
  ;; Usually, you should check the token in DB or other credentials here.
  ;; Here we assume you use basic auth with a fixed key "123"
  (match token
    (('basic . key) (string=? key "123"))
    (else #f)))

(get "/api/test"
  #:with-auth `(token authorization ,checker)
  #:session #t ; this must be enabled together with #:with-auth
  (lambda (rc)
    "Hello, World!"))

So we can check it with curl like this:

curl localhost:3000/api/test -H "Authorization: Basic 123"

Never the end

This post doesn't cover all the situations, and we didn't talk about security issues.

The security and oauth will be in other future posts.

Author: Artanis Dev Team

Created: 2025-10-14 Tue 23:08

Validate