ory / kratos

The most scalable and customizable identity server on the market. Replace your Homegrown, Auth0, Okta, Firebase with better UX and DX. Has all the tablestakes: Passkeys, Social Sign In, Multi-Factor Auth, SMS, SAML, TOTP, and more. Written in Go, cloud native, headless, API-first. Available as a service on Ory Network and for self-hosters.
https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=kratos
Apache License 2.0
11.34k stars 964 forks source link

Implement Hydra integration #273

Closed sczj closed 2 years ago

sczj commented 4 years ago

Configure Hydra's authorization authentication, how it should be configured in the profile

dezeroku commented 2 years ago

@aeneasr on a side note, do you plan a tagged release with this change anytime soon or there's something else that you'd like to merge first?

sidharthramesh commented 2 years ago

Wow! This is great! Any idea when there'll be a release @aeneasr?

sidharthramesh commented 2 years ago
  1. hydra v2.0.1 with urls.consent pointing to consent API as usual and login/logout URLs pointing to Kratos' selfservice UI

@dezeroku, what did you use as the logout URL in the hydra config? The Kratos self-service UI doesn't have a specific logout endpoint and generates the logout link dynamically. My hydra.yml looks like this:

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 # will not work??

secrets:
  system:
    - youReallyNeedToChangeThis

oidc:
  subject_identifiers:
    supported_types:
      - pairwise
      - public
    pairwise:
      salt: youReallyNeedToChangeThis
dezeroku commented 2 years ago

@sidharthramesh I am afraid you're right, I've used the selfservice's /logout (nonexistent) endpoint in hydra configuration and never really got around to testing it. At this state of testing I was mostly using Kratos' logout option on selfservice's welcome page and cleaning cookies from time to time.

I think a good starting point would be to use the /logout endpoint from the consent app? And if you get into a weird login redirection loop in which:

  1. You log out of Hydra
  2. Get redirected to Kratos' selfservice login page
  3. Get redirected to Hydra with cookie that Kratos remembered from last login
  4. Get token from Hydra again with the same user that was logged out

you might consider modifying the logic in https://github.com/ory/hydra-login-consent-node/blob/master/src/routes/logout.ts#L63 to also redirect to Kratos' logout URL defined as in selfservice: https://github.com/ory/kratos-selfservice-ui-node/blob/master/src/routes/welcome.ts#L22 Not sure if this would be needed at all or Hydra would handle both Hydra and Kratos "sessions" logout behind the scenes.

bbroniewski commented 2 years ago

This is where official versions and guide is required, of course we may all together try to solve those issues, and reverse ingeener all of those, even looking at the code but those problems were already solved. @aeneasr could you tell us about plans? Is there a plan to publish it (versions and guide) or there was some decision change to not publish it to open community right now?

genedy2017 commented 1 year ago

I've been able to integrate kratos as IDP provider for hydra and it works beautifully after kratos 0.11.0 release. Thanks so much for the great work you do guys!

One issue I encountered while implementing is that the oauth2_provider.url is being used when communicating with both hydra public and admin endpoints. In our production setup, hydra admin endpoints are not accessible publicly and are served through a different url than the public endpoints.

Thus when kratos tried to access hydra admin endpoint (to get login_challenge details) using the public url configured in oauth2_provider.url, it reported a 404 error. I was able to fix that using an nginx rewrite rule for now, but it would be nice to have a separate config maybe oauth2_provider.admin_url or so.

vinckr commented 1 year ago

Hey, that is good to hear @genedy2017 ! If you ever want to do a writeup of your setup for other community members, feel free to reach out to me :)

As for your issue, I think it would be best if you open a new discussion (or even a bug report?) for it. Some crazies like me follow all the threads, but maintainers mainly look at new issues or discussions.

genedy2017 commented 1 year ago

Hey @vinckr,

If you ever want to do a writeup of your setup for other community members, feel free to reach out to me :)

Would love to! I'll prepare an article and reach out to you within a few weeks.

I'll open a new issue as per your recommendation.

LasseRosenow commented 1 year ago

@bbroniewski it works for me (meaning I can obtain tokens from hydra, based on Kratos' user data/login process) with the following config:

1. kratos docker image built from master, oauth2_provider.url in config set to hydra's admin API

2. consent v2.0.1, pointing to Hydra's admin/public API as usual

3. kratos' selfservice UI image :latest, pointing to kratos admin/public API as usual

4. hydra v2.0.1 with urls.consent pointing to consent API as usual and login/logout URLs pointing to Kratos' selfservice UI

What confuses me about "2.", that why do I need the hydra "consent" user-interface if I use the kratos ui for login and logout?

dezeroku commented 1 year ago

@LasseRosenow If I remember correctly, kratos ui didn't have "consent" screen implemented at the time (maybe it's changed now, I don't know). So for the "full OAuth experience" you needed both login screen (kratos UI) and consent screen (consent service)

muwiess commented 1 year ago

Hey @vinckr,

If you ever want to do a writeup of your setup for other community members, feel free to reach out to me :)

Would love to! I'll prepare an article and reach out to you within a few weeks.

I'll open a new issue as per your recommendation.

I'm new to install Kratos and Hydra implementation, wondering if there's a guide that anyone can share?

vinckr commented 1 year ago

Hello @muwiess Check out this discussion for the "status quo": https://github.com/ory/kratos/discussions/2976#discussioncomment-4507555

There is no "out-of-the-box" guide yet, but there are a lot of examples around. If you would like to contribute a guide for the self-hosted integration of Kratos/Hydra, I would be happy to help you! Otherwise you can test out the integration without any previous for on the managed Ory Network

jmls commented 1 year ago

@muwiess were you able to get this working ? If so, do you have any docs we can steal ? :)

ellicodan commented 1 year ago

@bbroniewski it works for me (meaning I can obtain tokens from hydra, based on Kratos' user data/login process) with the following config:

  1. kratos docker image built from master, oauth2_provider.url in config set to hydra's admin API
  2. consent v2.0.1, pointing to Hydra's admin/public API as usual
  3. kratos' selfservice UI image :latest, pointing to kratos admin/public API as usual
  4. hydra v2.0.1 with urls.consent pointing to consent API as usual and login/logout URLs pointing to Kratos' selfservice UI

@dezeroku I'm struggling to get this to work. I have tried integrating Hydra/Kratos login flow using hydra-login-consent-node + kratos-selfservice-ui-node, and also my custom UI.

docker compose file:

name: ory

networks:
  postgres:
    external: true
    name: postgres_internal
  public:
    external: true
    name: public
  mailhog:
    external: true
    name: mailhog_internal

volumes:
  kratos:
    name: kratos_data

x-kratos-volumes: &kratos-volumes
  volumes:
    - ${DEV_CONFIG_DIR}/kratos:/etc/config/kratos
    - kratos:/var/lib/sqlite
    - kratos:/home/ory

services:
  kratos-migrate:
    # image: oryd/kratos:${KRATOS_VERSION}
    image: ellicodan/kratos:latest
    container_name: ${KRATOS_HOST_NAME}-migrate
    <<: *kratos-volumes
    command: --config /etc/config/kratos/kratos.yml migrate sql --read-from-env --yes
    environment:
      - DSN=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST_NAME}:${POSTGRES_PORT}/${KRATOS_DB_NAME}?sslmode=disable&max_conns=20&max_idle_conns=4 
      - LOG_LEVEL=info
    networks:
      - postgres

  hydra-migrate:
    image: oryd/hydra:$HYDRA_VERSION
    container_name: ${HYDRA_HOST_NAME}-migrate
    command: migrate sql --read-from-env --yes
    environment:
      - DSN=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST_NAME}:${POSTGRES_PORT}/${HYDRA_DB_NAME}?sslmode=disable&max_conns=20&max_idle_conns=4 
    networks:
      - postgres

  kratos:
    # image: oryd/kratos:${KRATOS_VERSION}
    image: ellicodan/kratos:latest
    container_name: ${KRATOS_HOST_NAME}
    <<: *kratos-volumes
    command: serve --config /etc/config/kratos/kratos.yml --watch-courier --dev
    environment:
      - DSN=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST_NAME}:${POSTGRES_PORT}/${KRATOS_DB_NAME}?sslmode=disable&max_conns=20&max_idle_conns=4  # Postgresql
      - LOG_LEVEL=info
    networks:
      - public
      - postgres
      - mailhog
    ports:
      - 4433:4433 # public
      - 4434:4434 # admin
    depends_on:
      kratos-migrate:
        condition: service_completed_successfully

  hydra:
    image: oryd/hydra:$HYDRA_VERSION
    container_name: $HYDRA_HOST_NAME
    volumes:
      - ${DEV_CONFIG_DIR}/hydra:/etc/config/hydra
    command: serve all --config /etc/config/hydra/hydra.yml --dev
    environment:
      - DSN=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST_NAME}:${POSTGRES_PORT}/${HYDRA_DB_NAME}?sslmode=disable&max_conns=20&max_idle_conns=4  # Postgresql
    ports:
      - 4444:4444 # public
      - 4445:4445 # admin
      - 5555:5555 # port for hydra token user
    networks:
      - public
      - postgres
    depends_on:
      hydra-migrate:
        condition: service_completed_successfully

kratos.yml file:

version: v0.11.1

dsn: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST_NAME}:${POSTGRES_PORT}/${KRATOS_DB_NAME}?sslmode=disable&max_conns=20&max_idle_conns=4

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

log:
  level: debug
  format: text
  leak_sensitive_values: true

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

selfservice:
  default_browser_return_url: http://localhost:3000/
  allowed_return_urls:
    - http://localhost:3000
  methods:
    profile:
      enabled: true
    password:
      enabled: true
      config:
        haveibeenpwned_enabled: true
        haveibeenpwned_host: api.pwnedpasswords.com
        max_breaches: 0
        ignore_network_errors: true
        min_password_length: 6
        identifier_similarity_check_enabled: true
    code:
      enabled: true
      config:
        lifespan: 24h
    link:
      enabled: true
      config:
        lifespan: 24h
    lookup_secret:
      enabled: false
    webauthn:
      enabled: false
    oidc:
      enabled: false

  flows:
    login:
      ui_url: http://localhost:3000/login
      lifespan: 15m
    logout:
      after:
        default_browser_return_url: http://localhost:3000/
    registration:
      ui_url: http://localhost:3000/registration
      lifespan: 15m
      after:
        password:
          hooks:
            - hook: session
    settings:
      ui_url: http://localhost:3000/settings
      privileged_session_max_age: 15m
      required_aal: highest_available
      after:
        default_browser_return_url: http://localhost:3000/settings
    recovery:
      enabled: true
      ui_url: http://localhost:3000/recovery
      use: code
      lifespan: 15m
      after:
        hooks:
          - hook: revoke_active_sessions
    verification:
      enabled: true
      ui_url: http://localhost:3000/verification
      use: link
      lifespan: 15m
      after:
        default_browser_return_url: http://localhost:3000/login
    error:
      ui_url: http://localhost:3000/error

oauth2_provider:
  url: http://localhost:4445
  headers: 
    Authorization: Bearer some-token

session:
  lifespan: 1h
  cookie:
    name: ory-session
    persistent: true

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

ciphers:
  algorithm: xchacha20-poly1305

hashers:
  algorithm: argon2
  argon2:
    iterations: 3
    memory: 64MB
    parallelism: 4
    key_length: 32
    salt_length: 16
    expected_duration: 500ms
    expected_deviation: 500ms
    dedicated_memory: 512MB

courier:
  smtp:
    connection_uri: smtp://mailhog:1026/?disable_starttls=true

hydra.yml file:

version: v2.0.3

dsn: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST_NAME}:${POSTGRES_PORT}/${HYDRA_DB_NAME}?sslmode=disable&max_conns=20&max_idle_conns=4

log:
  leak_sensitive_values: true
  format: text
  level: debug

serve:
  cookies:
    same_site_mode: Lax

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

urls:
  login: http://localhost:3000/login
  logout: http://localhost:3000/logout
  post_logout_redirect: http://localhost:3000/
  consent: http://localhost:3001/consent
  error: http://localhost:3000/error
  self:
    admin: https://localhost:4445/
    public: http://localhost:4444/
    issuer: http://localhost:4444/

ttl:
  access_token: 1h
  refresh_token: 1h
  id_token: 1h
  auth_code: 1h

oauth2:
  expose_internal_errors: true

secrets:
  cookie:
    - youReallyNeedToChangeThis
  system:
    - youReallyNeedToChangexhis

Here is an example response that I receive for a login flow:

{
  "id": "155217b1-3cd0-4ef1-a1d7-329f65b03071",
  "oauth2_login_challenge": null,
  "type": "browser",
  "expires_at": "2023-03-23T05:31:24.562209Z",
  "issued_at": "2023-03-23T05:16:24.562209Z",
  "request_url": "http://localhost:4433/self-service/login/browser",
  "ui": {
    "action": "http://localhost:4433/self-service/login?flow=155217b1-3cd0-4ef1-a1d7-329f65b03071",
    "method": "POST",
    "nodes": [
      {
        "type": "input",
        "group": "default",
        "attributes": {
          "name": "csrf_token",
          "type": "hidden",
          "value": "ogjV/+bvQFp7a8R+9aUR/Nx1podKqCJTh/Zdkmd0EtNwP+hoo71RFmziv6RA2wpqUKftalBOTP/DA12Nj5GJsQ==",
          "required": true,
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {}
      },
      {
        "type": "input",
        "group": "default",
        "attributes": {
          "name": "identifier",
          "type": "text",
          "value": "",
          "required": true,
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1070004,
            "text": "ID",
            "type": "info"
          }
        }
      },
      {
        "type": "input",
        "group": "password",
        "attributes": {
          "name": "password",
          "type": "password",
          "required": true,
          "autocomplete": "current-password",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1070001,
            "text": "Password",
            "type": "info"
          }
        }
      },
      {
        "type": "input",
        "group": "password",
        "attributes": {
          "name": "method",
          "type": "submit",
          "value": "password",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1010001,
            "text": "Sign in",
            "type": "info",
            "context": {}
          }
        }
      }
    ]
  },
  "created_at": "2023-03-23T05:16:24.578615Z",
  "updated_at": "2023-03-23T05:16:24.578615Z",
  "refresh": false,
  "requested_aal": "aal1",
  "instanceOf": "KratosForm"
}

oauth2_login_challenge is null. I'm not sure if it's supposed to return hydra's login_challenge. A successful login only returns a kratos session cookie, no tokens are returned. Any assistance would be greatly appreciated.

ellicodan commented 1 year ago

oauth2_login_challenge is null. I'm not sure if it's supposed to return hydra's login_challenge. A successful login only returns a kratos session cookie, no tokens are returned. Any assistance would be greatly appreciated.

I managed to solve my issues. In kratos.yml, oauth2_provider.url needs to be http://hydra:4445 instead of localhost. Another issue was that my secrets.system changed since I initialized my postgres db, so I needed to reset the db.