{"id":198,"date":"2025-02-23T23:12:12","date_gmt":"2025-02-23T23:12:12","guid":{"rendered":"https:\/\/neutronxinnovation.com\/blog\/?p=198"},"modified":"2025-02-23T23:13:59","modified_gmt":"2025-02-23T23:13:59","slug":"install-ory-kratos-api-ui-with-email-setup","status":"publish","type":"post","link":"https:\/\/neutronxinnovation.com\/blog\/2025\/02\/23\/install-ory-kratos-api-ui-with-email-setup\/","title":{"rendered":"Install Ory Kratos (API &#038; UI) with email setup"},"content":{"rendered":"<p>This article is in continuation to <a href=\"https:\/\/neutronxinnovation.com\/blog\/2025\/02\/02\/install-nginx-on-ubuntu-route-traffic-for-different-subdomains-on-same-ip-address\/\" target=\"_blank\" rel=\"noopener\">Install nginx &amp; route traffic for different subdomains<\/a>. This post also contains some pre-configuration for Ory Kratos to be accessible on subdomain. Therefore do read it so that you don&#8217;t get confused.<\/p>\n<p>&nbsp;<\/p>\n<p>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:<\/p>\n<ol>\n<li>Create a schema\/database in our RDBMS (I am using MySQL) for Kratos.<\/li>\n<li>Install and configure Kratos.<\/li>\n<li>Create Kratos service for autostart (systemd).<\/li>\n<li>Install and configure Kratos UI.<\/li>\n<li>Create service for autostart (systemd).<\/li>\n<li>Get the email service ready to send signup\/recovery emails (I am using <a href=\"https:\/\/mailtrap.io?ref=harsh39\" target=\"_blank\" rel=\"nofollow noopener sponsored\">Mailtrap.io<\/a>).<\/li>\n<li>Test everything together!<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<p>Let&#8217;s get started&#8230;<\/p>\n<p>&nbsp;<\/p>\n<h3>Create a schema\/Database in our RDBMS for Kratos<\/h3>\n<p>This was straightforward. I already had installed MySQL as mentioned here: <a href=\"https:\/\/neutronxinnovation.com\/blog\/2025\/01\/24\/install-ubuntu-and-expose-raspberry-pi-to-internet\/\" target=\"_blank\" rel=\"nofollow noopener\">Install Ubuntu and expose Raspberry Pi to internet<\/a><\/p>\n<p>I used following SQL commands:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">create schema kratos collate utf8mb4_unicode_520_ci;\r\n\r\ncreate user kratos\r\n    identified by 'MyAmazinglySecurePassword';\r\n\r\ngrant all privileges on kratos.* to kratos;<\/pre>\n<p>&nbsp;<\/p>\n<p>Here I just created an empty schema\/database named &#8216;kratos&#8217; and also a user named &#8216;kratos&#8217;. Last command grants all privileges on that database to kratos.<\/p>\n<p>&nbsp;<\/p>\n<h3>Install and configure Kratos<\/h3>\n<p>I followed the official Kratos installation guide for most steps. You can see it here if you want: <a href=\"https:\/\/www.ory.sh\/docs\/kratos\/guides\/deploy-kratos-example\" target=\"_blank\" rel=\"nofollow noopener\">Deploy to production | Ory<\/a><\/p>\n<p>But I am mentioning all steps which I followed regardless, since I am writing the article \ud83d\ude1b<\/p>\n<h4><\/h4>\n<p><strong>Step 1: Setup a new user for kratos in linux<\/strong><\/p>\n<p>Of course we would do this so that kratos runs with it&#8217;s own user privileges. As I mentioned in <a href=\"https:\/\/neutronxinnovation.com\/blog\/2025\/01\/24\/install-ubuntu-and-expose-raspberry-pi-to-internet\/\" target=\"_blank\" rel=\"nofollow noopener\">Install Ubuntu and expose Raspberry Pi to internet<\/a> article, I am having all my apps in \/serverapps\/ directory. Hence I will use following directory structure to hold all auth related apps:<\/p>\n<p>&nbsp;<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-204\" src=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-1.jpg\" alt=\"directory structure for kratos installation\" width=\"526\" height=\"137\" srcset=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-1.jpg 526w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-1-300x78.jpg 300w\" sizes=\"(max-width: 526px) 100vw, 526px\"><\/p>\n<p>&nbsp;<\/p>\n<p>So let&#8217;s create the directory structure:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\">cd \/serverapps\r\nmkdir -p auth\/ory\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Now let&#8217;s create a new user &#8216;kratos&#8217; with disabled login and assign the owner of that directory as that user itself.<\/p>\n<p>This command does it in one go:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\">useradd -s \/bin\/false -m -d \/serverapps\/auth\/ory\/kratos kratos<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Step 2: Create a folder for Kratos installation\/config files<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\">mkdir \/serverapps\/ory\/kratos\/{bin,config}<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Step 3: Install Ory Kratos<\/strong><\/p>\n<p>This is straightforward as Kratos is simply just one executable. To see what is the latest version: <a href=\"https:\/\/github.com\/ory\/kratos\/tags\" target=\"_blank\" rel=\"nofollow noopener\">Release v1.3.1 \u00b7 ory\/kratos \u00b7 GitHub<\/a><\/p>\n<p>Their GitHub has a pre-built binary available for each operating system:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-206 aligncenter\" src=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-2.jpg\" alt=\"Kratos os versions on GitHub\" width=\"1193\" height=\"480\" srcset=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-2.jpg 2269w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-2-300x121.jpg 300w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-2-1024x412.jpg 1024w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-2-768x309.jpg 768w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-2-1536x618.jpg 1536w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-2-2048x824.jpg 2048w\" sizes=\"(max-width: 1193px) 100vw, 1193px\"><\/p>\n<p>&nbsp;<\/p>\n<p>I have a Raspberry Pi and hence I am using Linux arm64 version.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\">cd \/serverapps\/auth\/ory\/kratos\/bin\r\n\r\n# Download\r\nwget https:\/\/github.com\/ory\/kratos\/releases\/download\/v1.3.1\/kratos_1.3.1-linux_arm64.tar.gz\r\n\r\n# Extract\r\ntar xfvz kratos_1.3.1-linux_64bit.tar.gz\r\n\r\n# Remove redundant files\r\nrm kratos_1.3.1-linux_64bit.tar.gz\r\nrm *md\r\nrm LICENSE<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Step 4: Download pre-built config files<\/strong><\/p>\n<p>Now we will download pre-built config files for Kratos under config directory.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">cd ..\/config\r\n\r\nwget https:\/\/raw.githubusercontent.com\/ory\/kratos\/v1.3.1\/contrib\/quickstart\/kratos\/email-password\/identity.schema.json\r\n\r\nwget https:\/\/raw.githubusercontent.com\/ory\/kratos\/v1.3.1\/contrib\/quickstart\/kratos\/email-password\/kratos.yml<\/pre>\n<p>&nbsp;<\/p>\n<ul>\n<li><strong>kratos.yml:<\/strong> This is the main configuration file for Kratos.<\/li>\n<li><strong>identity.schema.json:<\/strong> This is default identity schema. In that we define the user identity fields and authentication related fields as well.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p><strong>Step 5: Configuring kratos.yml<\/strong><\/p>\n<p>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.<\/p>\n<p>Don&#8217;t worry. I will explain each step in detail.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">version: v0.13.0\r\n\r\ndsn: mysql:\/\/kratos:MyAwesomeSecurePassword@tcp(localhost:3306)\/kratos?max_conns=20&amp;max_idle_conns=4\r\n\r\nserve:\r\n  public:\r\n    base_url: https:\/\/auth.neutronxinnovation.com\/\r\n    host: 127.0.0.1\r\n    cors:\r\n      enabled: true\r\n      allowed_origins:\r\n        - http:\/\/localhost\r\n        - https:\/\/localhost\r\n        - https:\/\/neutronxinnovation.com\r\n        - https:\/\/www.neutronxinnovation.com\r\n        - https:\/\/auth.neutronxinnovation.com\r\n        - https:\/\/www.auth.neutronxinnovation.com\r\n      allowed_methods:\r\n        - POST\r\n        - GET\r\n        - PUT\r\n        - PATCH\r\n        - DELETE\r\n        - OPTIONS\r\n      allowed_headers:\r\n        - Authorization\r\n        - Cookie\r\n        - Content-Type\r\n      exposed_headers:\r\n        - Content-Type\r\n        - Set-Cookie\r\n\r\n  admin:\r\n    base_url: http:\/\/127.0.0.1:4434\/\r\n    host: 127.0.0.1\r\n\r\nsession:\r\n    cookie:\r\n      domain: neutronxinnovation.com\r\n      same_site: Lax\r\n      name: wme_auth\r\n    whoami:\r\n      required_aal: aal1\r\n\r\nselfservice:\r\n  default_browser_return_url: https:\/\/auth.neutronxinnovation.com\/auth\/\r\n  allowed_return_urls:\r\n    - https:\/\/auth.neutronxinnovation.com\r\n\r\n  methods:\r\n    password:\r\n      enabled: true\r\n    lookup_secret:\r\n      enabled: true\r\n    link:\r\n      enabled: true\r\n    code:\r\n      enabled: true\r\n\r\n  flows:\r\n    error:\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/errors\r\n\r\n    settings:\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/settings\r\n      privileged_session_max_age: 15m\r\n      required_aal: highest_available\r\n\r\n    recovery:\r\n      enabled: true\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/recovery\r\n      use: code\r\n\r\n    verification:\r\n      enabled: true\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/verification\r\n      use: code\r\n      after:\r\n        default_browser_return_url: https:\/\/neutronxinnovation.com\r\n\r\n    logout:\r\n      after:\r\n        default_browser_return_url: https:\/\/auth.neutronxinnovation.com\/auth\/login\r\n\r\n    login:\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/login\r\n      lifespan: 10m\r\n\r\n    registration:\r\n      lifespan: 10m\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/registration\r\n      after:\r\n        password:\r\n          hooks:\r\n            - hook: session\r\n            - hook: show_verification_ui\r\n\r\nlog:\r\n  level: trace\r\n  format: text\r\n  leak_sensitive_values: true\r\n\r\nsecrets:\r\n  cookie:\r\n    - XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\n  cipher:\r\n    - XXXXXXXXXXXXXXXXXXXXXXXXXXX\r\n\r\nciphers:\r\n  algorithm: xchacha20-poly1305\r\n\r\nhashers:\r\n  algorithm: argon2\r\n  argon2:\r\n    parallelism: 1\r\n    memory: 64MB\r\n    iterations: 3\r\n    salt_length: 16\r\n    key_length: 32\r\n\r\nidentity:\r\n  default_schema_id: user_v0\r\n  schemas:\r\n    - id: user_v0\r\n      url: file:\/\/\/serverapps\/auth\/ory\/kratos\/config\/identity.schema.json\r\n\r\nfeature_flags:\r\n  - use_continue_with_transitions: true\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Explanations:<\/strong><\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">dsn: mysql:\/\/kratos:MyAwesomeSecurePassword@tcp(localhost:3306)\/kratos?max_conns=20&amp;max_idle_conns=4<\/pre>\n<p>This line above configures the connection for Kratos with our RDBMS instance. In my case, MySQL. Change it as it is for you.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">serve:\r\n  public:\r\n    base_url: https:\/\/auth.neutronxinnovation.com\/\r\n    host: 127.0.0.1\r\n    cors:\r\n      enabled: true\r\n      allowed_origins:\r\n        - http:\/\/localhost\r\n        - https:\/\/localhost\r\n        - https:\/\/neutronxinnovation.com\r\n        - https:\/\/www.neutronxinnovation.com\r\n        - https:\/\/auth.neutronxinnovation.com\r\n        - https:\/\/www.auth.neutronxinnovation.com\r\n      allowed_methods:\r\n        - POST\r\n        - GET\r\n        - PUT\r\n        - PATCH\r\n        - DELETE\r\n        - OPTIONS\r\n      allowed_headers:\r\n        - Authorization\r\n        - Cookie\r\n        - Content-Type\r\n      exposed_headers:\r\n        - Content-Type\r\n        - Set-Cookie\r\n\r\n  admin:\r\n    base_url: http:\/\/127.0.0.1:4434\/\r\n    host: 127.0.0.1<\/pre>\n<p>In this section, we&#8217;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.<\/p>\n<p>CORS stands for cross-origin resource sharing. In summary, let&#8217;s say we have 2 websites abc.com &amp; xyz.com. In your browser, you visit abc.com. Let&#8217;s assume abc.com website UI is calling some API via xhr request at xyz.com&#8217;s backend. This is called a cross-origin request since the root domains involved are different.<\/p>\n<p>Now by default, the browser will not directly call the xyz.com&#8217;s api and instead, it will send an OPTIONS request. xyz.com&#8217;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.<\/p>\n<p>In the code above, we&#8217;re configuring the domains, request types and headers which are allowed for CORS requests. For admin, we don&#8217;t want it to be accessible at all from outside and hence we will only make it accessible from localhost.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">session:\r\n    cookie:\r\n      domain: neutronxinnovation.com\r\n      same_site: Lax\r\n      name: wme_auth\r\n    whoami:\r\n      required_aal: aal1<\/pre>\n<p>This section above configures session &amp; cookie policies. Modify it to strict if your require. But it is up-to you as I wanted Lax. For more info, visit: <a href=\"https:\/\/www.ory.sh\/docs\/kratos\/guides\/configuring-cookies\" target=\"_blank\" rel=\"nofollow noopener\">Cookie settings | Ory<\/a><\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">selfservice:\r\n  default_browser_return_url: https:\/\/auth.neutronxinnovation.com\/auth\/\r\n  allowed_return_urls:\r\n    - https:\/\/auth.neutronxinnovation.com\r\n\r\n  methods:\r\n    password:\r\n      enabled: true\r\n    lookup_secret:\r\n      enabled: true\r\n    link:\r\n      enabled: true\r\n    code:\r\n      enabled: true\r\n\r\n  flows:\r\n    error:\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/errors\r\n\r\n    settings:\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/settings\r\n      privileged_session_max_age: 15m\r\n      required_aal: highest_available\r\n\r\n    recovery:\r\n      enabled: true\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/recovery\r\n      use: code\r\n\r\n    verification:\r\n      enabled: true\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/verification\r\n      use: code\r\n      after:\r\n        default_browser_return_url: https:\/\/neutronxinnovation.com\r\n\r\n    logout:\r\n      after:\r\n        default_browser_return_url: https:\/\/auth.neutronxinnovation.com\/auth\/login\r\n\r\n    login:\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/login\r\n      lifespan: 10m\r\n\r\n    registration:\r\n      lifespan: 10m\r\n      ui_url: https:\/\/auth.neutronxinnovation.com\/auth\/registration\r\n      after:\r\n        password:\r\n          hooks:\r\n            - hook: session\r\n            - hook: show_verification_ui<\/pre>\n<p>In this code above, we are configuring linkage with ui as well as which auth methods are enabled:<\/p>\n<ul>\n<li>[Line 6 &#8211; 14] We have enabled password based auth, login link based auth and login code based auth.<\/li>\n<li>[Line 16 &#8211; 52] UI flows are configured in these lines.<\/li>\n<li>[Line 25 &#8211; 28] Here we&#8217;re configuring account recovery to be enabled (When you forget password, a code will be sent to your email).<\/li>\n<li>[Line 30 &#8211; 35] This is for a code to be sent in a verification email when a user signs up with email.<\/li>\n<li>[Line 37] Self explanatory. This tells where to redirect a user after logout.<\/li>\n<li>[Line 45-52] Here we&#8217;re configuring such that user is shown email verification ui after they sign up.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">secrets:\r\n  cookie:\r\n    - XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\n  cipher:\r\n    - XXXXXXXXXXXXXXXXXXXXXXXXXXX\r\n\r\nciphers:\r\n  algorithm: xchacha20-poly1305\r\n\r\nhashers:\r\n  algorithm: argon2\r\n  argon2:\r\n    parallelism: 1\r\n    memory: 64MB\r\n    iterations: 3\r\n    salt_length: 16\r\n    key_length: 32\r\n<\/pre>\n<p>In this code above, we are configuring security related settings:<\/p>\n<ul>\n<li>[Line 1-8] Here we are configuring secrets for cookies. You can generate them using <code class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">openssl rand -hex 32<\/code>where 32 is the number of digits. For cipher you can have 16.\n<ul>\n<li>For more information, please visit: <a href=\"https:\/\/www.ory.sh\/docs\/kratos\/guides\/select-cipher-algorithm\" target=\"_blank\" rel=\"nofollow noopener\">Cipher algorithm settings | Ory<\/a><\/li>\n<\/ul>\n<\/li>\n<li>[Line 10 &#8211; 17] This is for configuring hashing algorithm for the passwords. My favourite is <strong>argon2id<\/strong> as it was the winner of password hashing tournament.\n<ul>\n<li>To find the values for configuration, visit: <a href=\"https:\/\/www.ory.sh\/docs\/self-hosted\/kratos\/configuration\/password\" target=\"_blank\" rel=\"nofollow noopener\">Passwords settings | Ory<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\">identity:\r\n  default_schema_id: user_v0\r\n  schemas:\r\n    - id: user_v0\r\n      url: file:\/\/\/serverapps\/auth\/ory\/kratos\/config\/identity.schema.json<\/pre>\n<p>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.<\/p>\n<p><code class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">default_schema_id:<\/code> 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.<\/p>\n<p>&nbsp;<\/p>\n<p><strong>Step 6: Configuring identity.schema.json<\/strong><\/p>\n<p>&nbsp;<\/p>\n<p>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: <a href=\"https:\/\/www.ory.sh\/docs\/kratos\/manage-identities\/customize-identity-schema#:~:text=This%20document%20explains%20how%20to%20customize%20your%20identity,your%20identities%20such%20as%20birthday%20or%20job%20title.\" target=\"_blank\" rel=\"nofollow noopener\">Learn how to customize Ory identity schemas | Ory<\/a><\/p>\n<p>&nbsp;<\/p>\n<p>I wanted following things:<\/p>\n<ul>\n<li>email-password combination for login<\/li>\n<li>2 extra fields: Name (First name and Last name) and Phone number.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Here is my identity schema:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">{\r\n  \"$id\": \"https:\/\/schemas.ory.sh\/presets\/kratos\/quickstart\/email-password\/identity.schema.json\",\r\n  \"$schema\": \"http:\/\/json-schema.org\/draft-07\/schema#\",\r\n  \"title\": \"Person\",\r\n  \"type\": \"object\",\r\n  \"properties\": {\r\n    \"traits\": {\r\n      \"type\": \"object\",\r\n      \"properties\": {\r\n        \"email\": {\r\n          \"type\": \"string\",\r\n          \"format\": \"email\",\r\n          \"title\": \"E-Mail\",\r\n          \"minLength\": 3,\r\n          \"ory.sh\/kratos\": {\r\n            \"credentials\": {\r\n              \"password\": {\r\n                \"identifier\": true\r\n              }\r\n            },\r\n            \"recovery\": {\r\n              \"via\": \"email\"\r\n            },\r\n            \"verification\": {\r\n              \"via\": \"email\"\r\n            }\r\n          }\r\n        },\r\n        \"phone\": {\r\n          \"type\": \"string\",\r\n          \"title\": \"Phone\",\r\n          \"format\": \"tel\"\r\n        },\r\n        \"name\": {\r\n          \"type\": \"object\",\r\n          \"required\": [\"first\", \"last\"],\r\n          \"properties\": {\r\n            \"first\": {\r\n              \"title\": \"First Name\",\r\n              \"type\": \"string\"\r\n            },\r\n            \"last\": {\r\n              \"title\": \"Last Name\",\r\n              \"type\": \"string\"\r\n            }\r\n          }\r\n        }\r\n      },\r\n      \"required\": [\"email\", \"name\", \"phone\"],\r\n      \"additionalProperties\": false\r\n    }\r\n  }\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Most of these are self explanatory. You can read it out and understand.<\/p>\n<p>&nbsp;<\/p>\n<p><strong>Step 7: Fix all permissions on directories and files<\/strong><\/p>\n<p>Since we are manually creating files, we need to make sure permissions are correct.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">cd \/serverapps\/auth\/ory\/\r\n\r\nchown -r kratos:kratos kratos<\/pre>\n<p>This will change ownership to kratos user for kratos and all it&#8217;s child directories\/files. -r is for recursive.<\/p>\n<h3><\/h3>\n<h3>Migrate Kratos DB Schema<\/h3>\n<p>Now next step is to allow Kratos to create the necessary tables in database. Kratos binary itself does it.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">\/serverapps\/auth\/ory\/kratos\/bin\/kratos -c \/serverapps\/auth\/ory\/kratos\/config\/kratos.yml migrate sql -y mysql:\/\/kratos:MyAwesomeSecurePassword\\$@tcp\\(localhost:3306\\)\/kratos<\/pre>\n<p>&nbsp;<\/p>\n<p>This will run the kratos sql migration and you should be able to see the tables generated in the schema we created.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-218 aligncenter\" src=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-3.jpg\" alt=\"kratos tables\" width=\"521\" height=\"955\"><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h3>Add service for kratos so that it starts automatically at system startup<\/h3>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">cd \/etc\/systemd\/system\r\nnano kratos.service\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Paste following content:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">[Unit]\r\nDescription=Kratos Service\r\nAfter=network.target\r\nStartLimitIntervalSec=0\r\n\r\n[Service]\r\nWorkingDirectory=\/serverapps\/auth\/ory\/kratos\/bin\r\nType=simple\r\nRestart=no\r\nUser=kratos\r\nExecStart=\/serverapps\/auth\/ory\/kratos\/bin\/kratos -c \/serverapps\/auth\/ory\/kratos\/config\/kratos.yml serve --watch-courier\r\n\r\n[Install]\r\nWantedBy=multi-user.target\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Then reload the systemd daemon so that it is aware that a new service has been added.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">systemctl daemon-reload\r\nsystemctl enable kratos.service<\/pre>\n<p>&nbsp;<\/p>\n<p>With this, we&#8217;re all set with Kratos. Now let&#8217;s install the Kratos UI.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h3>Download and Install Kratos UI<\/h3>\n<p>Prerequisite<span style=\"color: #ff0000;\">*<\/span>: You must have installed Node already. To do that follow: <a href=\"https:\/\/linuxconfig.org\/how-to-install-node-js-on-ubuntu-24-04\">How to Install Node.js on Ubuntu 24.04 &#8211; LinuxConfig<\/a>. I am using Node v20.<\/p>\n<p>&nbsp;<\/p>\n<p>Similar to what we did for Kratos, let&#8217;s create a new user &#8216;kratos-ui&#8217; with disabled login and assign the owner of that directory as that user itself.<\/p>\n<p>This command does it in one go:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\">useradd -s \/bin\/false -m -d \/serverapps\/auth\/ory\/kratos-ui kratos-ui<\/pre>\n<p>&nbsp;<\/p>\n<p>Now let&#8217;s download Kratos UI and move all the extracted files from subdirectory to kratos-ui directory<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">cd \/serverapps\/auth\/ory\/kratos-ui\r\nwget https:\/\/github.com\/ory\/kratos-selfservice-ui-node\/archive\/refs\/tags\/v1.3.1.zip\r\nunzip v1.3.1.zip\r\nrm  v1.3.1.zip\r\nmv kratos-selfservice-ui-node-1.3.1\/ .\r\nnpm install<\/pre>\n<p>&nbsp;<\/p>\n<p>Since we are manually creating files, we need to make sure permissions are correct.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">cd \/serverapps\/auth\/ory\r\n\r\nchown -r kratos-ui:kratos-ui kratos-ui<\/pre>\n<p>&nbsp;<\/p>\n<p>This will change ownership to kratos-ui user for kratos-ui and all it&#8217;s child directories\/files. -r is for recursive.<\/p>\n<p>Now let&#8217;s add service for kratos-ui so that it runs at system startup.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">cd \/etc\/systemd\/system\r\nnano kratos-ui.service\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Paste following content. Replace neutronxinnovation.com with your domain name:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">[Unit]\r\nDescription=Kratos UI Service\r\nAfter=network.target\r\nStartLimitIntervalSec=0\r\n\r\n[Service]\r\nType=simple\r\nRestart=no\r\nUser=kratos-ui\r\nExecStart=npm start\r\nEnvironment=KRATOS_PUBLIC_URL=http:\/\/127.0.0.1:4433\r\nEnvironment=KRATOS_BROWSER_URL=https:\/\/auth.neutronxinnovation.com\r\nWorkingDirectory=\/serverapps\/auth\/ory\/kratos-ui\r\n\r\n[Install]\r\nWantedBy=multi-user.target\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Save it. Then reload the systemd daemon so that it is aware that a new service has been added.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">systemctl daemon-reload\r\nsystemctl enable kratos-ui.service<\/pre>\n<p>&nbsp;<\/p>\n<p>With this, our kratos-ui is also done. Restart the server.<\/p>\n<p>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:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-220 aligncenter\" src=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-4.jpg\" alt=\"Kratos UI Screen\" width=\"1310\" height=\"627\"><\/p>\n<p>&nbsp;<\/p>\n<p>If you go to sign up page, you will see that the fields you configured in identity schema json are visible. Magic isn&#8217;t it! :<\/p>\n<p>&nbsp;<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-242 aligncenter\" src=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-6.jpg\" alt=\"kratos signup page\" width=\"1603\" height=\"645\" srcset=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-6.jpg 2443w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-6-300x121.jpg 300w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-6-1024x412.jpg 1024w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-6-768x309.jpg 768w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-6-1536x618.jpg 1536w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-6-2048x824.jpg 2048w\" sizes=\"(max-width: 1603px) 100vw, 1603px\"><\/p>\n<p>&nbsp;<\/p>\n<p>You can use the sign-up as well, but you won&#8217;t receive the verification email as we haven&#8217;t yet configured the email sending service.<\/p>\n<p>&nbsp;<\/p>\n<h3>Configure email service for Kratos (using <a href=\"https:\/\/mailtrap.io?ref=harsh39\" target=\"_blank\" rel=\"nofollow noopener sponsored\">Mailtrap.io<\/a>)<\/h3>\n<p>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 <a href=\"https:\/\/mailtrap.io?ref=harsh39\" target=\"_blank\" rel=\"nofollow noopener sponsored\">Mailtrap.io<\/a> here for sending emails. It is free upto 1000 emails per month and I found it&#8217;s UX and APIs straightforward. But you can use anything you want.<\/p>\n<p>Ory Kratos uses 2 methods to send email: SMTP or API. I am using API method.<\/p>\n<p>&nbsp;<\/p>\n<p><strong>Step 1: Setup <a href=\"https:\/\/mailtrap.io?ref=harsh39\" target=\"_blank\" rel=\"nofollow noopener sponsored\">MailTrap.io<\/a> account and get API details<\/strong><\/p>\n<p>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.<\/p>\n<p>&nbsp;<\/p>\n<p>First you need to create an account. After that, when you reach the dashboard, under &#8220;Email Testing&#8221; section, you will see &#8220;Inboxes&#8221;. Click on that.<\/p>\n<p>Go to Integration -&gt; API section and copy the <strong>Host<\/strong> and <strong>Api-token<\/strong>. Keep these with you as we would need this in next steps.<\/p>\n<p>&nbsp;<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-235 aligncenter\" src=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-4.jpg\" alt=\"mailtrap test\" width=\"1589\" height=\"698\" srcset=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-4.jpg 2376w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-4-300x132.jpg 300w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-4-1024x450.jpg 1024w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-4-768x337.jpg 768w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-4-1536x674.jpg 1536w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-4-2048x899.jpg 2048w\" sizes=\"(max-width: 1589px) 100vw, 1589px\"><\/p>\n<p>&nbsp;<\/p>\n<p><strong>Step 2: Create a mail jsoonnet file<\/strong><\/p>\n<p>Why this step is needed? Let me explain with an example.<\/p>\n<p>Let&#8217;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?<\/p>\n<p>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.<\/p>\n<p>&nbsp;<\/p>\n<p>Mailtrap expects the email request to be in this format below. If you&#8217;re using any other service provider, check their documentation.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">{\r\n    \"from\": {\r\n        \"email\": \"no-reply@neutronxinnovation.com\",\r\n        \"name\": \"neutronxinnovation.com\"\r\n    },\r\n    \"to\": [\r\n        {\r\n            \"email\": \"samplerecepient@something.com\"\r\n        }\r\n    ],\r\n    \"subject\": \"Please verify your email address\",\r\n    \"html\": \"......... html email body.........\"\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>Now let&#8217;s create a jsonnet file so that ory can send email in this format:<\/p>\n<p>&nbsp;<\/p>\n<p>Create a new file:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\">nano \/serverapps\/auth\/ory\/kratos\/config\/mailtrap.jsonnet<\/pre>\n<p>&nbsp;<\/p>\n<p>Paste the following contents:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">function(ctx) {\r\n  \"from\": {\r\n    \"email\": \"no-reply@neutronxinnovation.com\",\r\n    \"name\": \"neutronxinnovation.com\"\r\n  },\r\n  \"to\": [\r\n    {\r\n      \"email\": ctx.recipient\r\n    }\r\n  ],\r\n  \"subject\": ctx.subject,\r\n  \"html\": ctx.body\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Save it. Now as usual, fix the permissions:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">cd \/serverapps\/auth\/ory\/kratos\/config \r\nchown -r kratos:kratos mailtrap.jsonnet<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Step 3: Add email configuration to kratos.yml<\/strong><\/p>\n<p>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.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\" data-enlighter-highlight=\"5,7,10\">courier:\r\n  delivery_strategy: http\r\n  http:\r\n    request_config:\r\n      url: https:\/\/sandbox.api.mailtrap.io\/api\/send\r\n      method: POST\r\n      body: file:\/\/\/serverapps\/auth\/ory\/kratos\/config\/mailtrap.jsonnet\r\n      headers:\r\n        Content-Type: application\/json\r\n        Api-Token: XXXXXXXXXXXXXXXXXXXXX\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Save the file. It should now look something like this:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-240 aligncenter\" src=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-5.jpg\" alt=\"kratos courier configuration\" width=\"968\" height=\"585\" srcset=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-5.jpg 968w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-5-300x181.jpg 300w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/kratos-5-768x464.jpg 768w\" sizes=\"(max-width: 968px) 100vw, 968px\"><\/p>\n<p>&nbsp;<\/p>\n<p>Now restart kratos service:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">systemctl restart kratos.service<\/pre>\n<p>&nbsp;<\/p>\n<h3>Test Sign up and emails<\/h3>\n<p>Test the sign up now. if everything goes right, you should see the account verification email under here:<\/p>\n<p>&nbsp;<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-236 aligncenter\" src=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-5.jpg\" alt=\"mailtrap-5\" width=\"905\" height=\"731\" srcset=\"https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-5.jpg 905w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-5-300x242.jpg 300w, https:\/\/neutronxinnovation.com\/blog\/wp-content\/uploads\/2025\/02\/mailtrap-5-768x620.jpg 768w\" sizes=\"(max-width: 905px) 100vw, 905px\"><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>With this, we have concluded the article. Stay tuned for more&#8230;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article is in continuation to Install nginx &amp; route [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_seopress_robots_primary_cat":"none","_seopress_titles_title":"Install Ory Kratos (API & UI) with email setup","_seopress_titles_desc":"In this post, we will see how we can install and Configure Ory Kratos on our server (Raspberry Pi 5 or any) including email sending using MailTrap.","_seopress_robots_index":""},"categories":[1],"tags":[8,10,18],"_links":{"self":[{"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/posts\/198"}],"collection":[{"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/comments?post=198"}],"version-history":[{"count":31,"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/posts\/198\/revisions"}],"predecessor-version":[{"id":243,"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/posts\/198\/revisions\/243"}],"wp:attachment":[{"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/media?parent=198"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/categories?post=198"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/neutronxinnovation.com\/blog\/wp-json\/wp\/v2\/tags?post=198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}