Ory Hydra + Kratos integration

3k Views Asked by At

In the release 2.0 of Ory Hydra was announced that they have now an integration with Kratos their identity provider. It is also mentioned that this is possible to achieve by doing some configuration "Ory Identities is now compatible with the Ory OAuth2 Login and Consent Flow. This means, for example, that Ory Kratos can be the login provider for Ory Hydra with a bit of configuration."

Have someone done this configuration, is there any example I can follow to use Kratos as identity provider for Hydra?

Release notes: https://github.com/ory/hydra/releases/tag/v2.0.0

So far I was able to setup a docker compose file where I have a Postgres database and I also have Hydra and Kratos. What I don't know is how to make them interact with each other.

3

There are 3 best solutions below

0
On BEST ANSWER

You must still handle the consent flow through Ory Kratos. You can refer to an example provided by the community here. Additionally, you can check out this project: https://github.com/lus/hydra-consent and the related issue: https://github.com/ory/kratos-selfservice-ui-node/issues/224.

In the future, it would be ideal to have a tutorial on how to set up the consent flow in the self-hosted section of Ory Hydra or Ory Kratos. This integration comes with Ory Network (https://console.ory.sh/) but requires additional coding to set up on-premises.

1
On

Kratos and Hydra need to be on the same Docker network, and then oauth2_provider.url needs to be set to http://hydra:4445 in Kratos.

In 2024, this works with Hydra v2.2.0 and Kratos v1.1.0

# quickstart.yml
###########################################################################
#######             FOR DEMONSTRATION PURPOSES ONLY                 #######
###########################################################################

version: "3.7"
services:

  ### KRATOS ###
  kratos-migrate:
    image: oryd/kratos:v1.1.0
    environment:
      - DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc
    volumes:
      - type: volume
        source: kratos-sqlite
        target: /var/lib/sqlite
        read_only: false
      - type: bind
        source: ./config
        target: /etc/config/kratos
    command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
    restart: on-failure
    networks:
      - intranet
  kratos-selfservice-ui-node:
    image: oryd/kratos-selfservice-ui-node:v1.1.0
    ports:
      - "4455:4455"
    environment:
      - PORT=4455
      - KRATOS_PUBLIC_URL=http://kratos:4433
      - KRATOS_BROWSER_URL=http://127.0.0.1:4433
      - COOKIE_SECRET=changeme
      - CSRF_COOKIE_NAME=cookie_name
      - CSRF_COOKIE_SECRET=changeme
      - DANGEROUSLY_DISABLE_SECURE_CSRF_COOKIES=true
    networks:
      - intranet
    restart: on-failure
  kratos:
    depends_on:
      - kratos-migrate
    image: oryd/kratos:v1.1.0
    ports:
      - '4433:4433' # public
      - '4434:4434' # admin
    restart: unless-stopped
    environment:
      - DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true
      - LOG_LEVEL=trace
    command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
    volumes:
      - type: volume
        source: kratos-sqlite
        target: /var/lib/sqlite
        read_only: false
      - type: bind
        source: ./config
        target: /etc/config/kratos
    networks:
      - intranet

  ### HYDRA ###
  
  hydra:
    image: oryd/hydra:v2.2.0
    ports:
      - "4444:4444" # Public port
      - "4445:4445" # Admin port
      - "5555:5555" # Port for hydra token user
    command: serve -c /etc/config/hydra/hydra.yml all --dev
    volumes:
      - type: volume
        source: hydra-sqlite
        target: /var/lib/sqlite
        read_only: false
      - type: bind
        source: ./config
        target: /etc/config/hydra
    environment:
      - DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true
    restart: unless-stopped
    depends_on:
      - hydra-migrate
    networks:
      - intranet
  hydra-migrate:
    image: oryd/hydra:v2.2.0
    environment:
      - DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true
    command: migrate -c /etc/config/hydra/hydra.yml sql -e --yes
    volumes:
      - type: volume
        source: hydra-sqlite
        target: /var/lib/sqlite
        read_only: false
      - type: bind
        source: ./config
        target: /etc/config/hydra
    restart: on-failure
    networks:
      - intranet
  consent:
    environment:
      - HYDRA_ADMIN_URL=http://hydra:4445
    image: oryd/hydra-login-consent-node:v2.2.0
    ports:
      - "3000:3000"
    restart: unless-stopped
    networks:
      - intranet

networks:
  intranet:
volumes:
  hydra-sqlite:
  kratos-sqlite:
# config/kratos.yml
version: v1.1.0

dsn: memory

serve:
  public:
    base_url: http://127.0.0.1:4433
    cors:
      enabled: true
  admin:
    base_url: http://127.0.0.1:4434

selfservice:
  default_browser_return_url: http://127.0.0.1:4455/
  allowed_return_urls:
    - http://127.0.0.1:4455

  methods:
    password:
      enabled: true
      config:
        min_password_length: 6
        identifier_similarity_check_enabled: false
        haveibeenpwned_enabled: false

  flows:
    error:
      ui_url: http://127.0.0.1:4455/error

    settings:
      ui_url: http://127.0.0.1:4455/settings
      privileged_session_max_age: 15m
      required_aal: highest_available

    logout:
      after:
        default_browser_return_url: http://127.0.0.1:4455/login

    login:
      ui_url: http://127.0.0.1:4455/login

    registration:
      ui_url: http://127.0.0.1:4455/registration
      after:
        password:
          hooks:
            - hook: session

log:
  format: text
  leak_sensitive_values: true

secrets:
  cookie:
    - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
  cipher:
    - 32-LONG-SECRET-NOT-SECURE-AT-ALL

identity:
  default_schema_id: default
  schemas: 
    - id: default
      url: file:///etc/config/kratos/identity.schema.json

courier:
  smtp:
    connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true

oauth2_provider:
  url: http://hydra:4445 # Integrate Kratos and Hydra
# config/hydra.yml
serve:
  cookies:
    same_site_mode: Lax

urls:
  self:
    issuer: http://127.0.0.1:4444
  consent: http://127.0.0.1:3000/consent
  login: http://127.0.0.1:4455/login
  logout: http://127.0.0.1:4455/logout
  identity_provider:
    publicUrl: http://127.0.0.1:4433
    url: http://127.0.0.1:4434


secrets:
  system:
    - youReallyNeedToChangeThis

oidc:
  subject_identifiers:
    supported_types:
      - pairwise
      - public
    pairwise:
      salt: youReallyNeedToChangeThis

config/identity.schema.json

{
  "$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
              }
            }
          }
        }
      },
      "required": [
        "email"
      ],
      "additionalProperties": false
    }
  }
}
1
On

@Hodgson's answer is a great start for Kratos v1.1.0 and Hydra v2.2.0. Unfortunately, the provided docker compose yaml and hydra config are using hydra-login-consent-node (port 3000) for the consent route -- which doesn't use Kratos identity traits for requested+granted scopes (e.g.: email, profile).

Kratos SSUI was recently updated to include its own consent route, and has also been updated to extract traits from Kratos for id_token, based on the requested+granted scope (email, profile). Review the code at: https://github.com/ory/kratos-selfservice-ui-node/blob/master/src/routes/consent.ts

Recommended changes to @Hodgson's answer:

Completely comment-out or remove the "consent" section from the docker compose file (quickstart.yml).

Change config/hydra.yml:

urls:
  # consent: http://127.0.0.1:3000/consent
  consent: http://127.0.0.1:4455/consent

To add First Name, Last Name (accessible via Kratos SSUI's consent route with "profile" scope) to your Kratos identity schema, see https://github.com/ory/kratos/blob/master/contrib/quickstart/kratos/email-password/identity.schema.json

The prior answer is also missing details on how to create an OIDC client:

docker ps
# Get ID of hydra container

docker exec -it HYDRA_CONTAINER_ID \
hydra create client \
-e http://hydra:4445 \
--token-endpoint-auth-method client_secret_basic \
--name "OIDC Client Name" \
--skip-consent=false \
--logo-uri https://www.example.com:8443/logo/image.png \
--policy-uri https://www.example.com:8443/privacy.html \
--tos-uri https://www.example.com:8443/terms.html \
--redirect-uri https://www.example.com:8443/auth/login/ory/callback \
--redirect-uri https://www.example.com:8443/app/index.html \
--redirect-uri http://127.0.0.1:5555/callback \
--redirect-uri http://127.0.0.1:4455 \
--grant-type "authorization_code,refresh_token" \
--response-type "code" \
--scope "openid,offline_access,email,profile" \
--format json-pretty

Set/include/exclude the URLs (logo, policy, tos, redirect/s) as needed.

To disable prompting the user to grant consent for the requested scopes, either set skip-consent=true when creating the OIDC client, or add an environment variable to the kratos section in quickstart.yml for TRUSTED_CLIENT_IDS, set to the OIDC Client IDs (comma-separated) that you want to auto-accept scope requests for (skipping the consent UI).

Quick tip: If you're using webauthn (Yubikey, Touch ID, etc) with Kratos, you'll need to change most of the references to localhost (instead of 127.0.0.1), and/or use a FQDN and SSL Certificate. Below is some quick code to get a short-lived cert from Let's Encrypt.

mkdir -p certbot/{etc,lib,log,opt}

docker run -it --rm \
--name=certbot --hostname=certbot \
-v ${PWD}/certbot/etc:/etc/letsencrypt \
-v ${PWD}/certbot/lib:/var/lib/letsencrypt \
-v ${PWD}/certbot/log:/var/log/letsencrypt \
certbot/certbot:v2.9.0 \
certonly \
--manual \
--preferred-challenges dns \
-d YOUR_DOMAIN_NAME \
--email YOUR_EMAIL_ADDRESS \
--agree-tos

At the DNS provider for YOUR_DOMAIN_NAME, add record (value provided by Certbot):

TXT _acme-challenge.SUB_DOMAIN CERTBOT_CHALLENGE

Manually verify the new TXT record, then proceed in Certbot

nslookup -q=txt _acme-challenge.SUB_DOMAIN.YOUR_TOPLEVEL_DOMAIN

Then you can add an entry to your hosts file (127.0.0.1 YOUR_DOMAIN_NAME) to ensure proper resolution locally, and use NGINX to proxy or host your web app, referencing the public (fullchain.pem) and private (privkey.pem) certs/keys in your certbot/etc/live/YOUR_DOMAIN_NAME folder. For access outside your network, add an A record with your external IP at the DNS provider for YOUR_DOMAIN_NAME, and add any necessary port forwarding rules in your ISP's router and/or your home router.

To get everything working in my setup, I also needed to change the Hydra urls.self.issuer to the FQDN (http://YOUR_DOMAIN_NAME:4444), and customize the docker compose config for the app using the OIDC Client so internal attempts to access the public Hydra URL (port 4444) would be re-routed through my host system, so redirects could be properly processed. This might not be needed if your app is running bare-metal or on a different docker network than your Ory services.

    extra_hosts:
      - "YOUR_DOMAIN_NAME:host-gateway"