Install Ory Kratos (API & UI) with email setup

This article is in continuation to Install nginx & route traffic for different subdomains. This post also contains some pre-configuration for Ory Kratos to be accessible on subdomain. Therefore do read it so that you don’t get confused.

 

In this post, we will see how we can install and Configure Ory Kratos on our server (Raspberry Pi 5 or any). This is going to be a little lengthy process. We will do it in following steps:

  1. Create a schema/database in our RDBMS (I am using MySQL) for Kratos.
  2. Install and configure Kratos.
  3. Create Kratos service for autostart (systemd).
  4. Install and configure Kratos UI.
  5. Create service for autostart (systemd).
  6. Get the email service ready to send signup/recovery emails (I am using Mailtrap.io).
  7. Test everything together!

 

Let’s get started…

 

Create a schema/Database in our RDBMS for Kratos

This was straightforward. I already had installed MySQL as mentioned here: Install Ubuntu and expose Raspberry Pi to internet

I used following SQL commands:

 

create schema kratos collate utf8mb4_unicode_520_ci;

create user kratos
    identified by 'MyAmazinglySecurePassword';

grant all privileges on kratos.* to kratos;

 

Here I just created an empty schema/database named ‘kratos’ and also a user named ‘kratos’. Last command grants all privileges on that database to kratos.

 

Install and configure Kratos

I followed the official Kratos installation guide for most steps. You can see it here if you want: Deploy to production | Ory

But I am mentioning all steps which I followed regardless, since I am writing the article ๐Ÿ˜›

Step 1: Setup a new user for kratos in linux

Of course we would do this so that kratos runs with it’s own user privileges. As I mentioned in Install Ubuntu and expose Raspberry Pi to internet article, I am having all my apps in /serverapps/ directory. Hence I will use following directory structure to hold all auth related apps:

 

directory structure for kratos installation

 

So let’s create the directory structure:

 

cd /serverapps
mkdir -p auth/ory

 

Now let’s create a new user ‘kratos’ with disabled login and assign the owner of that directory as that user itself.

This command does it in one go:

useradd -s /bin/false -m -d /serverapps/auth/ory/kratos kratos

 

Step 2: Create a folder for Kratos installation/config files

mkdir /serverapps/ory/kratos/{bin,config}

 

Step 3: Install Ory Kratos

This is straightforward as Kratos is simply just one executable. To see what is the latest version: Release v1.3.1 ยท ory/kratos ยท GitHub

Their GitHub has a pre-built binary available for each operating system:

Kratos os versions on GitHub

 

I have a Raspberry Pi and hence I am using Linux arm64 version.

 

cd /serverapps/auth/ory/kratos/bin

# Download
wget https://github.com/ory/kratos/releases/download/v1.3.1/kratos_1.3.1-linux_arm64.tar.gz

# Extract
tar xfvz kratos_1.3.1-linux_64bit.tar.gz

# Remove redundant files
rm kratos_1.3.1-linux_64bit.tar.gz
rm *md
rm LICENSE

 

Step 4: Download pre-built config files

Now we will download pre-built config files for Kratos under config directory.

 

cd ../config

wget https://raw.githubusercontent.com/ory/kratos/v1.3.1/contrib/quickstart/kratos/email-password/identity.schema.json

wget https://raw.githubusercontent.com/ory/kratos/v1.3.1/contrib/quickstart/kratos/email-password/kratos.yml

 

  • kratos.yml: This is the main configuration file for Kratos.
  • identity.schema.json: This is default identity schema. In that we define the user identity fields and authentication related fields as well.

 

Step 5: Configuring kratos.yml

Replace kratos.yml with these contents. Replace neutronxinnovation.com with the domain which you bought and for which we did the nginx config in previous post.

Don’t worry. I will explain each step in detail.

 

version: v0.13.0

dsn: mysql://kratos:MyAwesomeSecurePassword@tcp(localhost:3306)/kratos?max_conns=20&max_idle_conns=4

serve:
  public:
    base_url: https://auth.neutronxinnovation.com/
    host: 127.0.0.1
    cors:
      enabled: true
      allowed_origins:
        - http://localhost
        - https://localhost
        - https://neutronxinnovation.com
        - https://www.neutronxinnovation.com
        - https://auth.neutronxinnovation.com
        - https://www.auth.neutronxinnovation.com
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Cookie
        - Content-Type
      exposed_headers:
        - Content-Type
        - Set-Cookie

  admin:
    base_url: http://127.0.0.1:4434/
    host: 127.0.0.1

session:
    cookie:
      domain: neutronxinnovation.com
      same_site: Lax
      name: wme_auth
    whoami:
      required_aal: aal1

selfservice:
  default_browser_return_url: https://auth.neutronxinnovation.com/auth/
  allowed_return_urls:
    - https://auth.neutronxinnovation.com

  methods:
    password:
      enabled: true
    lookup_secret:
      enabled: true
    link:
      enabled: true
    code:
      enabled: true

  flows:
    error:
      ui_url: https://auth.neutronxinnovation.com/auth/errors

    settings:
      ui_url: https://auth.neutronxinnovation.com/auth/settings
      privileged_session_max_age: 15m
      required_aal: highest_available

    recovery:
      enabled: true
      ui_url: https://auth.neutronxinnovation.com/auth/recovery
      use: code

    verification:
      enabled: true
      ui_url: https://auth.neutronxinnovation.com/auth/verification
      use: code
      after:
        default_browser_return_url: https://neutronxinnovation.com

    logout:
      after:
        default_browser_return_url: https://auth.neutronxinnovation.com/auth/login

    login:
      ui_url: https://auth.neutronxinnovation.com/auth/login
      lifespan: 10m

    registration:
      lifespan: 10m
      ui_url: https://auth.neutronxinnovation.com/auth/registration
      after:
        password:
          hooks:
            - hook: session
            - hook: show_verification_ui

log:
  level: trace
  format: text
  leak_sensitive_values: true

secrets:
  cookie:
    - XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  cipher:
    - XXXXXXXXXXXXXXXXXXXXXXXXXXX

ciphers:
  algorithm: xchacha20-poly1305

hashers:
  algorithm: argon2
  argon2:
    parallelism: 1
    memory: 64MB
    iterations: 3
    salt_length: 16
    key_length: 32

identity:
  default_schema_id: user_v0
  schemas:
    - id: user_v0
      url: file:///serverapps/auth/ory/kratos/config/identity.schema.json

feature_flags:
  - use_continue_with_transitions: true

 

Explanations:

 

dsn: mysql://kratos:MyAwesomeSecurePassword@tcp(localhost:3306)/kratos?max_conns=20&max_idle_conns=4

This line above configures the connection for Kratos with our RDBMS instance. In my case, MySQL. Change it as it is for you.

 

serve:
  public:
    base_url: https://auth.neutronxinnovation.com/
    host: 127.0.0.1
    cors:
      enabled: true
      allowed_origins:
        - http://localhost
        - https://localhost
        - https://neutronxinnovation.com
        - https://www.neutronxinnovation.com
        - https://auth.neutronxinnovation.com
        - https://www.auth.neutronxinnovation.com
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Cookie
        - Content-Type
      exposed_headers:
        - Content-Type
        - Set-Cookie

  admin:
    base_url: http://127.0.0.1:4434/
    host: 127.0.0.1

In this section, we’re configuring the base url (for which kratos will be served on), host (local machine ip on which it would be listening for requests) and CORS.

CORS stands for cross-origin resource sharing. In summary, let’s say we have 2 websites abc.com & xyz.com. In your browser, you visit abc.com. Let’s assume abc.com website UI is calling some API via xhr request at xyz.com’s backend. This is called a cross-origin request since the root domains involved are different.

Now by default, the browser will not directly call the xyz.com’s api and instead, it will send an OPTIONS request. xyz.com’s backend server will respond to that OPTIONS request telling that whether any other origin is allowed to call this server or not. Based on this response, the browser will decide whether it can proceed with that request. This is kind of a process by which resources should be shared when requests are cross-origin.

In the code above, we’re configuring the domains, request types and headers which are allowed for CORS requests. For admin, we don’t want it to be accessible at all from outside and hence we will only make it accessible from localhost.

 

session:
    cookie:
      domain: neutronxinnovation.com
      same_site: Lax
      name: wme_auth
    whoami:
      required_aal: aal1

This section above configures session & cookie policies. Modify it to strict if your require. But it is up-to you as I wanted Lax. For more info, visit: Cookie settings | Ory

 

selfservice:
  default_browser_return_url: https://auth.neutronxinnovation.com/auth/
  allowed_return_urls:
    - https://auth.neutronxinnovation.com

  methods:
    password:
      enabled: true
    lookup_secret:
      enabled: true
    link:
      enabled: true
    code:
      enabled: true

  flows:
    error:
      ui_url: https://auth.neutronxinnovation.com/auth/errors

    settings:
      ui_url: https://auth.neutronxinnovation.com/auth/settings
      privileged_session_max_age: 15m
      required_aal: highest_available

    recovery:
      enabled: true
      ui_url: https://auth.neutronxinnovation.com/auth/recovery
      use: code

    verification:
      enabled: true
      ui_url: https://auth.neutronxinnovation.com/auth/verification
      use: code
      after:
        default_browser_return_url: https://neutronxinnovation.com

    logout:
      after:
        default_browser_return_url: https://auth.neutronxinnovation.com/auth/login

    login:
      ui_url: https://auth.neutronxinnovation.com/auth/login
      lifespan: 10m

    registration:
      lifespan: 10m
      ui_url: https://auth.neutronxinnovation.com/auth/registration
      after:
        password:
          hooks:
            - hook: session
            - hook: show_verification_ui

In this code above, we are configuring linkage with ui as well as which auth methods are enabled:

  • [Line 6 – 14] We have enabled password based auth, login link based auth and login code based auth.
  • [Line 16 – 52] UI flows are configured in these lines.
  • [Line 25 – 28] Here we’re configuring account recovery to be enabled (When you forget password, a code will be sent to your email).
  • [Line 30 – 35] This is for a code to be sent in a verification email when a user signs up with email.
  • [Line 37] Self explanatory. This tells where to redirect a user after logout.
  • [Line 45-52] Here we’re configuring such that user is shown email verification ui after they sign up.

 

secrets:
  cookie:
    - XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  cipher:
    - XXXXXXXXXXXXXXXXXXXXXXXXXXX

ciphers:
  algorithm: xchacha20-poly1305

hashers:
  algorithm: argon2
  argon2:
    parallelism: 1
    memory: 64MB
    iterations: 3
    salt_length: 16
    key_length: 32

In this code above, we are configuring security related settings:

  • [Line 1-8] Here we are configuring secrets for cookies. You can generate them using openssl rand -hex 32where 32 is the number of digits. For cipher you can have 16.
  • [Line 10 – 17] This is for configuring hashing algorithm for the passwords. My favourite is argon2id as it was the winner of password hashing tournament.

 

identity:
  default_schema_id: user_v0
  schemas:
    - id: user_v0
      url: file:///serverapps/auth/ory/kratos/config/identity.schema.json

This is extremely important section. This is where we configure the structure of the identities (user properties) which are stored and shown on UI. Here I am providing the path of the configuration file.

default_schema_id: Value for this is upto you. Think of a scenario when you have to change something in user identity somewhere later in time. In this section, we can even provide multiple schemas but only 1 can be default.

 

Step 6: Configuring identity.schema.json

 

This is an open topic tbh. What fields you want to have. What authentication methods you want to have etc. There is a lot we can do in identity schema. Hence I will mention here what I wanted and how I configured it. If you want to learn in detail, check out the full documentation: Learn how to customize Ory identity schemas | Ory

 

I wanted following things:

  • email-password combination for login
  • 2 extra fields: Name (First name and Last name) and Phone number.

 

Here is my identity schema:

{
  "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Person",
  "type": "object",
  "properties": {
    "traits": {
      "type": "object",
      "properties": {
        "email": {
          "type": "string",
          "format": "email",
          "title": "E-Mail",
          "minLength": 3,
          "ory.sh/kratos": {
            "credentials": {
              "password": {
                "identifier": true
              }
            },
            "recovery": {
              "via": "email"
            },
            "verification": {
              "via": "email"
            }
          }
        },
        "phone": {
          "type": "string",
          "title": "Phone",
          "format": "tel"
        },
        "name": {
          "type": "object",
          "required": ["first", "last"],
          "properties": {
            "first": {
              "title": "First Name",
              "type": "string"
            },
            "last": {
              "title": "Last Name",
              "type": "string"
            }
          }
        }
      },
      "required": ["email", "name", "phone"],
      "additionalProperties": false
    }
  }
}

 

Most of these are self explanatory. You can read it out and understand.

 

Step 7: Fix all permissions on directories and files

Since we are manually creating files, we need to make sure permissions are correct.

cd /serverapps/auth/ory/

chown -r kratos:kratos kratos

This will change ownership to kratos user for kratos and all it’s child directories/files. -r is for recursive.

Migrate Kratos DB Schema

Now next step is to allow Kratos to create the necessary tables in database. Kratos binary itself does it.

 

/serverapps/auth/ory/kratos/bin/kratos -c /serverapps/auth/ory/kratos/config/kratos.yml migrate sql -y mysql://kratos:MyAwesomeSecurePassword\$@tcp\(localhost:3306\)/kratos

 

This will run the kratos sql migration and you should be able to see the tables generated in the schema we created.

kratos tables

 

 

Add service for kratos so that it starts automatically at system startup

 

cd /etc/systemd/system
nano kratos.service

 

Paste following content:

[Unit]
Description=Kratos Service
After=network.target
StartLimitIntervalSec=0

[Service]
WorkingDirectory=/serverapps/auth/ory/kratos/bin
Type=simple
Restart=no
User=kratos
ExecStart=/serverapps/auth/ory/kratos/bin/kratos -c /serverapps/auth/ory/kratos/config/kratos.yml serve --watch-courier

[Install]
WantedBy=multi-user.target

 

Then reload the systemd daemon so that it is aware that a new service has been added.

systemctl daemon-reload
systemctl enable kratos.service

 

With this, we’re all set with Kratos. Now let’s install the Kratos UI.

 

 

Download and Install Kratos UI

Prerequisite*: You must have installed Node already. To do that follow: How to Install Node.js on Ubuntu 24.04 – LinuxConfig. I am using Node v20.

 

Similar to what we did for Kratos, let’s create a new user ‘kratos-ui’ with disabled login and assign the owner of that directory as that user itself.

This command does it in one go:

useradd -s /bin/false -m -d /serverapps/auth/ory/kratos-ui kratos-ui

 

Now let’s download Kratos UI and move all the extracted files from subdirectory to kratos-ui directory

cd /serverapps/auth/ory/kratos-ui
wget https://github.com/ory/kratos-selfservice-ui-node/archive/refs/tags/v1.3.1.zip
unzip v1.3.1.zip
rm  v1.3.1.zip
mv kratos-selfservice-ui-node-1.3.1/ .
npm install

 

Since we are manually creating files, we need to make sure permissions are correct.

cd /serverapps/auth/ory

chown -r kratos-ui:kratos-ui kratos-ui

 

This will change ownership to kratos-ui user for kratos-ui and all it’s child directories/files. -r is for recursive.

Now let’s add service for kratos-ui so that it runs at system startup.

cd /etc/systemd/system
nano kratos-ui.service

 

Paste following content. Replace neutronxinnovation.com with your domain name:

[Unit]
Description=Kratos UI Service
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=no
User=kratos-ui
ExecStart=npm start
Environment=KRATOS_PUBLIC_URL=http://127.0.0.1:4433
Environment=KRATOS_BROWSER_URL=https://auth.neutronxinnovation.com
WorkingDirectory=/serverapps/auth/ory/kratos-ui

[Install]
WantedBy=multi-user.target

 

Save it. Then reload the systemd daemon so that it is aware that a new service has been added.

systemctl daemon-reload
systemctl enable kratos-ui.service

 

With this, our kratos-ui is also done. Restart the server.

Now when you visit your auth.yourdomain.com/auth, and if you did everything right, you should be able to see the following login screen:

Kratos UI Screen

 

If you go to sign up page, you will see that the fields you configured in identity schema json are visible. Magic isn’t it! :

 

kratos signup page

 

You can use the sign-up as well, but you won’t receive the verification email as we haven’t yet configured the email sending service.

 

Configure email service for Kratos (using Mailtrap.io)

To be honest, I was stuck at this step for 1 whole month due to a miss-configuration. I will tell you what I did which worked for me. I am using Mailtrap.io here for sending emails. It is free upto 1000 emails per month and I found it’s UX and APIs straightforward. But you can use anything you want.

Ory Kratos uses 2 methods to send email: SMTP or API. I am using API method.

 

Step 1: Setup MailTrap.io account and get API details

Please note I am mentioning instructions here for setting up a test email account so that you can test emails. For a real email account (which sends email to real email addresses), you have to verify your domain. The website itself has all the instructions for that.

 

First you need to create an account. After that, when you reach the dashboard, under “Email Testing” section, you will see “Inboxes”. Click on that.

Go to Integration -> API section and copy the Host and Api-token. Keep these with you as we would need this in next steps.

 

mailtrap test

 

Step 2: Create a mail jsoonnet file

Why this step is needed? Let me explain with an example.

Let’s say your email service provider has an api via which you can send emails. There would be fields like emailTo, emailFrom, subject, body. But now how do you tell Ory courier service that it has to call that API with request body in this format to your email provider?

Well, for this, Ory provides flexibility for you to configure a template. There we can tell Ory what request body structure it has to use to call that email API and it will fill in the details automatically in the placeholders. Let me show you how I created one for myself.

 

Mailtrap expects the email request to be in this format below. If you’re using any other service provider, check their documentation.

{
    "from": {
        "email": "[email protected]",
        "name": "neutronxinnovation.com"
    },
    "to": [
        {
            "email": "[email protected]"
        }
    ],
    "subject": "Please verify your email address",
    "html": "......... html email body........."
}

 

Now let’s create a jsonnet file so that ory can send email in this format:

 

Create a new file:

nano /serverapps/auth/ory/kratos/config/mailtrap.jsonnet

 

Paste the following contents:

function(ctx) {
  "from": {
    "email": "[email protected]",
    "name": "neutronxinnovation.com"
  },
  "to": [
    {
      "email": ctx.recipient
    }
  ],
  "subject": ctx.subject,
  "html": ctx.body
}

 

Save it. Now as usual, fix the permissions:

cd /serverapps/auth/ory/kratos/config 
chown -r kratos:kratos mailtrap.jsonnet

 

Step 3: Add email configuration to kratos.yml

Edit the kratos.yml and paste following content after the identity section or at the end of the file. Replace the url and Api-token value with the Api-token from mailtrap which we copied earlier.

courier:
  delivery_strategy: http
  http:
    request_config:
      url: https://sandbox.api.mailtrap.io/api/send
      method: POST
      body: file:///serverapps/auth/ory/kratos/config/mailtrap.jsonnet
      headers:
        Content-Type: application/json
        Api-Token: XXXXXXXXXXXXXXXXXXXXX

 

Save the file. It should now look something like this:

kratos courier configuration

 

Now restart kratos service:

systemctl restart kratos.service

 

Test Sign up and emails

Test the sign up now. if everything goes right, you should see the account verification email under here:

 

mailtrap-5

 

 

With this, we have concluded the article. Stay tuned for more…