ory / kratos

Next-gen identity server replacing your Auth0, Okta, Firebase with hardened security and PassKeys, SMS, OIDC, Social Sign In, MFA, FIDO, TOTP and OTP, WebAuthn, passwordless and much more. Golang, headless, API-first. Available as a worry-free SaaS with the fairest pricing on the market!
https://www.ory.sh/kratos/?utm_source=github&utm_medium=banner&utm_campaign=kratos
Apache License 2.0
11.06k stars 954 forks source link

[With Hdyra]Kratos return null for login_challenge when previous OIDC for the same client not complete. #4024

Open EverettSummer opened 1 month ago

EverettSummer commented 1 month ago

Preflight checklist

Ory Network Project

No response

Describe the bug

I'm using an ODIC certified library to build a test project to test my self-hosted kratos+hydra service, Since I'm developing my own UI part of the Kratos and consent page. so the oauth2 flow doesn't complete when reaching the consent page. After I updated the consent page. I tried again from the beginning of the flow, but it didn't work. the Kratos returned null for the login_challenge.

but the Kratos' log and Hydra's log return HTTP status 200. So I don't know what's wrong. the only way to solve this issue was to go to the database and delete rows in the table hydra_oauth2_authentication_session. then the login_challenge works again.

Reproducing the bug

  1. start an ODIC client with authorization code flow (my example: https://github.com/EverettSummer/angular-openid-example, use npm start and click login on the page)

Relevant log output

**kratos log:** https://gist.github.com/EverettSummer/87b7b5976ba21e658c21149f96612484

**hydra log:** https://gist.github.com/EverettSummer/85fc890b7b11833ab21a1d16c2e020ce

**1st request:** 
http://127.0.0.1:4444/oauth2/auth?client_id=c117b407-a075-4d56-91ad-96dbba56e832&redirect_uri=http%3A%2F%2Flocalhost%3A4200&response_type=code&scope=openid%20offline_access%20offline&nonce=91bd81ebfd4da754d47d9335fc595e4955ehL3Pn1&state=66efffef5c9c06a71bfc57a1cbb40da83d38wdZJL&code_challenge=Zq1iD9GOc1mnvUXMyjL1U6B2MmqemUOi_wj9c0vum_E&code_challenge_method=S256

**1st response: **
HTTP/1.1 302 Found
Cache-Control: private, no-cache, no-store, must-revalidate
Content-Type: text/html; charset=utf-8
Location: http://127.0.0.1:3000/login?login_challenge=YnG-8e7cu1u7jrrsIgyy_onOcA6yEiE6T_mgIXs-Bi9wU7A1jaXe9_edcYJ_AM_uhYozsMGJZIgDll2lpypX3rK_jpPBkteM2_zoFqKunW_Xv7IUwafP1MG-pA6AQ4dvk1CvCjL4ubAEDcDc0V6b1Vzekf8MDBCr5FJ4_PYVFx5oxPJakqhUyCWPkEHCBet27ch5FTqSPif-BTN7Srrvbzf1aBc9MTSgiko2ThURA8x6LKq3Jbn_2Ix0TxRI8q5UN6G5rLe2yvNHdxMjZ2G-hCil3noGw9XglLA4MzR8GTeedY84h8M9Ya6iDbH0jrRGkS0ym07_7kP6MCb1C-ZgnAnXrZDLPBi-b-JEnWCyx9qeoflzOoNLiw2BYFTxfD26bm45Q95FiMKUDVlf6UH6W96pauLv_EZAcl4_jSH7s14eNlf13n-mZuZ7WvHijbmMZBT_XF90q47QOjmn6TuMg2YtOkTKKnquhO06rzdhm647SbanjjbNmpeCGt9EA3aNeI0r7nFe2-kolDc-oZ1kH365bJ7RSobZ8fXoy9EBWE8Yb_4pPZZ3oHnF77_KaKDYTfYFzMpK6ezS20v4jWt2P5qHSQ_2ENF_Sg0Q8t8O4AReyynkH1c7HUeZOtFOdo8u4Rqm-ZFBKTMxUXH03E1-TqHEpkxNSLtA2Abgp_x6DssrklOex5i4vsgDvQR-Nl8G-4nHb9brmtL-hgjPJ-d5O5R5oWPvizHKwYHyAuzkYPa92EDMUcRy1mvWX7BQDRNHgR143bIcL-m-7PknEd7DKGxAdjNioeO4gEs3kAXx1Fu1OuBzxx5rBb0FqIiOM2RDjCARuI6kdI7b28l93Y-GCu_VkdsZPyZCt0RvFiyzMHtdlMp5HoSUr-5ULYRhjZzCnfi-E1zFL-bFVc4iuA3_RwbO-n9dukvGhHPDDUE-SG4LfFcY2t6eEPzl6R7kMIvfxrStsPnE_DyDzvzm2IMGexUXod6HW-zJyVFzQktwN3b5ApevRDa09zC1mxbXVtfdPrq-8jhjaWpPVO_FQPh0V7yjIrj25IpjjbzXw1K-DZADMcCdug9ZKDbYcDBACQ4roioL_XXiBs4oPcSfld8Iv94_FOCHlan4EPINE9EdB1M2crjt8gWL7zZhKizkHXhs2rXPwvtkKNjYQM0Aws0btawjAOqOiZeSyKiviS5se646EAmKR02_mQMYmOkF9C18oRIi3CtCKXC89ZGHh7hs5SLk9OJg7So6xJHMWOAMV8y8BJUzhz1LBFITdNbV8Rk7yPkTo2gO31PmUP3TZRt9DophCg6EVvWLu_S5g_90nVfdk3FI61xFS_4glbhKkB2py_j4H9uzSwE5UH9duNiLmEwPP0khUrEN-tRYYTQ_cdDYIrBYRDDs4z-VXwvtpv26RnoEOSWjztf3euGZ6aFCZdIY_UY5iYRn3CSRboPtfd7T99kaqqx_eNnDr2F-r6dGsTrmuomFePmUly8UIOG3rBqJOnUiNNJxCBJVguoPWPKkHo9Tv20qlMIdi3Bhq3sbgETioi8EDSlfv6ZzD08yYhtqKd7ltDqXxxqPtyBzvMNy00CsLqai1eQZSdJ4g58HdBowXy_RKnSGA1TcV5R3J5qEARJcYbvH8w%3D%3D
Set-Cookie: ory_hydra_login_csrf_dev_2023525599=MTcyMjc1MTU4OHxUZTIxWTVsXzU3M04xalI0YXFib2RhcGdlbUc3U2xfMUNVc0w4WnpkUmY2Y0RRbjk1MU5pZElNMDQxNU9LTDYyZzNReXBLTUxWQ2ZwWDJKcnhyckFGRjZtMkU3TUM0TzJIU3U0cG9hZzBTaDdhVVE4SUZZd05CbEl5U0VZfOrXSFbKjjQRBcxNlSs8SgjqOdb2RX3MCr_PdrN-dzEj; Path=/; Expires=Sun, 04 Aug 2024 06:36:28 GMT; Max-Age=1800; HttpOnly; SameSite=Lax
Vary: Origin
Date: Sun, 04 Aug 2024 06:06:28 GMT
Content-Length: 1707

**2nd request:**  
http://127.0.0.1:3000/login?login_challenge=YnG-8e7cu1u7jrrsIgyy_onOcA6yEiE6T_mgIXs-Bi9wU7A1jaXe9_edcYJ_AM_uhYozsMGJZIgDll2lpypX3rK_jpPBkteM2_zoFqKunW_Xv7IUwafP1MG-pA6AQ4dvk1CvCjL4ubAEDcDc0V6b1Vzekf8MDBCr5FJ4_PYVFx5oxPJakqhUyCWPkEHCBet27ch5FTqSPif-BTN7Srrvbzf1aBc9MTSgiko2ThURA8x6LKq3Jbn_2Ix0TxRI8q5UN6G5rLe2yvNHdxMjZ2G-hCil3noGw9XglLA4MzR8GTeedY84h8M9Ya6iDbH0jrRGkS0ym07_7kP6MCb1C-ZgnAnXrZDLPBi-b-JEnWCyx9qeoflzOoNLiw2BYFTxfD26bm45Q95FiMKUDVlf6UH6W96pauLv_EZAcl4_jSH7s14eNlf13n-mZuZ7WvHijbmMZBT_XF90q47QOjmn6TuMg2YtOkTKKnquhO06rzdhm647SbanjjbNmpeCGt9EA3aNeI0r7nFe2-kolDc-oZ1kH365bJ7RSobZ8fXoy9EBWE8Yb_4pPZZ3oHnF77_KaKDYTfYFzMpK6ezS20v4jWt2P5qHSQ_2ENF_Sg0Q8t8O4AReyynkH1c7HUeZOtFOdo8u4Rqm-ZFBKTMxUXH03E1-TqHEpkxNSLtA2Abgp_x6DssrklOex5i4vsgDvQR-Nl8G-4nHb9brmtL-hgjPJ-d5O5R5oWPvizHKwYHyAuzkYPa92EDMUcRy1mvWX7BQDRNHgR143bIcL-m-7PknEd7DKGxAdjNioeO4gEs3kAXx1Fu1OuBzxx5rBb0FqIiOM2RDjCARuI6kdI7b28l93Y-GCu_VkdsZPyZCt0RvFiyzMHtdlMp5HoSUr-5ULYRhjZzCnfi-E1zFL-bFVc4iuA3_RwbO-n9dukvGhHPDDUE-SG4LfFcY2t6eEPzl6R7kMIvfxrStsPnE_DyDzvzm2IMGexUXod6HW-zJyVFzQktwN3b5ApevRDa09zC1mxbXVtfdPrq-8jhjaWpPVO_FQPh0V7yjIrj25IpjjbzXw1K-DZADMcCdug9ZKDbYcDBACQ4roioL_XXiBs4oPcSfld8Iv94_FOCHlan4EPINE9EdB1M2crjt8gWL7zZhKizkHXhs2rXPwvtkKNjYQM0Aws0btawjAOqOiZeSyKiviS5se646EAmKR02_mQMYmOkF9C18oRIi3CtCKXC89ZGHh7hs5SLk9OJg7So6xJHMWOAMV8y8BJUzhz1LBFITdNbV8Rk7yPkTo2gO31PmUP3TZRt9DophCg6EVvWLu_S5g_90nVfdk3FI61xFS_4glbhKkB2py_j4H9uzSwE5UH9duNiLmEwPP0khUrEN-tRYYTQ_cdDYIrBYRDDs4z-VXwvtpv26RnoEOSWjztf3euGZ6aFCZdIY_UY5iYRn3CSRboPtfd7T99kaqqx_eNnDr2F-r6dGsTrmuomFePmUly8UIOG3rBqJOnUiNNJxCBJVguoPWPKkHo9Tv20qlMIdi3Bhq3sbgETioi8EDSlfv6ZzD08yYhtqKd7ltDqXxxqPtyBzvMNy00CsLqai1eQZSdJ4g58HdBowXy_RKnSGA1TcV5R3J5qEARJcYbvH8w%3D%3D

logs for the login_challenge request (since it's server-side request, I have to print the header of request and header and body of the response in the console)

(GET)http://127.0.0.1:4433/self-service/login/browser
req headers before:  _HttpHeaders {
  normalizedNames: Map(0) {},
  lazyUpdate: [ { name: 'Accept', value: 'application/json', op: 's' } ],
  headers: Map(0) {},
  lazyInit: _HttpHeaders {
    normalizedNames: Map(0) {},
    lazyUpdate: null,
    headers: Map(0) {}
  }
}
httpclient: http://127.0.0.1:4433/self-service/login/browser
req headers after:  _HttpHeaders {
  normalizedNames: Map(0) {},
  lazyUpdate: [
    {
      name: 'Cookie',
      value: 'csrf_token_806060ca5bf70dff3caa0e5c860002aade9d470a5a4dce73bcfa7ba10778f481=WbY3pAn2cgITovTWhVrcX9s3EDXAWxLjbqhQsnx2WhA=; session=64aba1d8-eeb0-4b30-a0d6-bccbd2376f87.BTkwQjmRDWR_iGC2AC70oHGWgIA; ory_kratos_session=MTcyMjY4MjMxNHxTYzhia1BMQ0NGdXQybFpYbXBDbHJXT05YaXlDb0FrZl9WeVUtcW9fZ25wbGNTUERlY2lleEtxMWlGS2did0ZWWURXbGpPSTNjdW5SeTNzdE0wU3FUZVpoSUxHN3g2MmZoWGp6bVZDQWRfUWhpZlU4UWszOEZXSHJ2Y1JHam9KcWlMLUJacUwtZU5tU2tnY1VscEpmTk8wWWM0YUlkRHozWU54S0F5X0diTTdhUmdQZjdndTdTX0FmTVdDY2hTNDhudkhUS2FrVGhMaGNJeHhzZ3d1TUxlLU9RVnJ0UFR3LWtRQ2Fmb0hfOTE2UzZ5Nmg1MTRNR1VuTmlXblJ3ZEZHNVBjRTNZaTlkVVhIMEdZVTl6Qjl8r7fhecV5yN2apXg3_q1wKvU2YvY56lNZjm-j6D77_wY=; ory_hydra_session_dev=MTcyMjY4MjMxNHxFczJBTUJWSFh3Ny1EYWgzcVRHM2duaEp1NnA4VEptQ191LUo0SElLcmplSENoUVZSWUtiM2dMbS1fbG1BelpLVmFicWUwOUxpR0tDZWYxbTdRd2Q4Z1BJbmJwbHlNYUFxTjB5VGZMUUN5TVZtUTdZaFVaSE5jZkJUNnJEVGlGTHxneD4VRxUqXVN28_KBl_w2wrdCkDKmovizoSp_uy8Wkw==; ory_hydra_login_csrf_dev_2023525599=MTcyMjc1MTU4OHxUZTIxWTVsXzU3M04xalI0YXFib2RhcGdlbUc3U2xfMUNVc0w4WnpkUmY2Y0RRbjk1MU5pZElNMDQxNU9LTDYyZzNReXBLTUxWQ2ZwWDJKcnhyckFGRjZtMkU3TUM0TzJIU3U0cG9hZzBTaDdhVVE4SUZZd05CbEl5U0VZfOrXSFbKjjQRBcxNlSs8SgjqOdb2RX3MCr_PdrN-dzEj',
      op: 's'
    },
    { name: 'Accept', value: 'application/json', op: 's' },
    {
      name: 'Accept-Encoding',
      value: 'gzip, deflate, br, zstd',
      op: 's'
    },
    { name: 'Accept-Language', value: 'en-US,en;q=0.5', op: 's' },
    { name: 'Cache-Control', value: 'no-cache', op: 's' },
    { name: 'Origin', value: 'http://127.0.0.1:3000', op: 's' },
    { name: 'Referer', value: 'http://127.0.0.1:3000/', op: 's' },
    {
      name: 'User-Agent',
      value: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0',
      op: 's'
    },
    { name: 'Sec-Fetch-Dest', value: 'empty', op: 's' },
    { name: 'Sec-Fetch-Mode', value: 'cors', op: 's' },
    { name: 'Sec-Fetch-Site', value: 'same-site', op: 's' }
  ],
  headers: Map(0) {},
  lazyInit: _HttpHeaders {
    normalizedNames: Map(1) { 'accept' => 'Accept' },
    lazyUpdate: null,
    headers: Map(1) { 'accept' => [Array] },
    lazyInit: null
  }
}
GET http://127.0.0.1:4433/self-service/login/browser
0 undefined
4 200
response header:  _HttpHeaders {
  normalizedNames: Map(8) {
    'access-control-allow-credentials' => 'access-control-allow-credentials',
    'access-control-allow-origin' => 'access-control-allow-origin',
    'access-control-expose-headers' => 'access-control-expose-headers',
    'cache-control' => 'cache-control',
    'content-length' => 'content-length',
    'content-type' => 'content-type',
    'date' => 'date',
    'vary' => 'vary'
  },
  lazyUpdate: null,
  headers: Map(8) {
    'access-control-allow-credentials' => [ 'true' ],
    'access-control-allow-origin' => [ 'http://127.0.0.1:3000' ],
    'access-control-expose-headers' => [ 'Content-Type' ],
    'cache-control' => [ 'private, no-cache, no-store, must-revalidate' ],
    'content-length' => [ '5' ],
    'content-type' => [ 'application/json; charset=utf-8' ],
    'date' => [ 'Sun, 04 Aug 2024 06:06:28 GMT' ],
    'vary' => [ 'Origin, Cookie' ]
  }
}
response body:  null

Relevant configuration

Kratos config

version: v1.0.0

dsn: memory

serve:
  public:
    base_url: http://127.0.0.1:4433/
    cors:
      allowed_origins:
        - http://127.0.0.1:3000
        - http://127.0.0.1:5000
        - http://127.0.0.1:3300
      enabled: true
  admin:
    base_url: http://127.0.0.1:4434/

selfservice:
  default_browser_return_url: http://127.0.0.1:3000/
  allowed_return_urls:
    - http://127.0.0.1:3000
    - http://127.0.0.1:4444
  methods:
    password:
      enabled: true
    totp:
      config:
        issuer: Kratos
      enabled: false
    lookup_secret:
      enabled: false
    link:
      enabled: false
    code:
      enabled: true

  flows:
    error:
      ui_url: http://127.0.0.1:3000/error

    settings:
      ui_url: http://127.0.0.1:3000/settings/account
      privileged_session_max_age: 15m
      required_aal: highest_available
      after:
        hooks:
          - hook: web_hook
            config:
              url: http://host.docker.internal:8085/internal/user/
              method: PUT
              body: file:///etc/config/kratos/segment_identity.jsonnet
              response:
                ignore: false
                parse: false
              auth:
                type: basic_auth
                config:
                  user: mira_internal
                  password: abc1234

    recovery:
      enabled: true
      ui_url: http://127.0.0.1:3000/recovery
      use: code

    verification:
      enabled: true
      ui_url: http://127.0.0.1:3000/verification
      use: code
      after:
        hooks:
          - hook: web_hook
            config:
              url: http://host.docker.internal:8085/internal/user
              method: POST
              body: file:///etc/config/kratos/segment_identity.jsonnet
              response:
                ignore: false
                parse: false
              auth:
                type: basic_auth
                config:
                  user: mira_internal
                  password: abc1234
        default_browser_return_url: http://127.0.0.1:3000/

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

    login:
      ui_url: http://127.0.0.1:3000/login
      lifespan: 10m
      after:
        password:
          hooks:
            - hook: require_verified_address

    registration:
      lifespan: 10m
      ui_url: http://127.0.0.1:3000/registration
      after:
        password:
          hooks:
            - hook: session
            - hook: show_verification_ui

log:
  level: debug
  format: text
  leak_sensitive_values: true

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

ciphers:
  algorithm: xchacha20-poly1305

hashers:
  algorithm: bcrypt
  bcrypt:
    cost: 8

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

cookies:
  same_site: Lax

# OAuth 2.0 Provider URL.
# If set, the login and registration flows will handle the Ory OAuth 2.0 & OpenID `login_challenge` query parameter to
#  serve as an OpenID Connect Provider. This URL should point to Ory Hydra when you are not running on the Ory Network
#  and be left untouched otherwise.
oauth2_provider:
  url: http://hydra:4445
#  headers:
#    Authorization: Bearer some-token
  override_return_to: true

hydra config:

log:
  leak_sensitive_values: true
serve:
  public:
    cors:
      enabled: true
      allowed_origins:
        - http://localhost:4200
        - http://127.0.0.1:3000
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
      allowed_headers:
        - "Authorization"
      exposed_headers:
        - "Content-Type"
      allow_credentials: true
      max_age: 0
      debug: false
  admin:
    cors:
      enabled: true
      allowed_origins:
        - "*"
        - http://127.0.0.1:3000
      allow_credentials: true
  cookies:
    same_site_mode: Lax
urls:
  self:
    issuer: http://127.0.0.1:4444
    public: http://127.0.0.1:4444
    admin: http://127.0.0.1:4445
  consent: http://127.0.0.1:3000/consent
  login: http://127.0.0.1:3000/login
  logout: http://127.0.0.1:3000/logout
  identity_provider:
    url: http://kratos:4434/
    publicUrl: http://kratos:4433/

secrets:
  system:
    - ProjectMiraDevSecretDoNotUseInProd

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

docker-compose.yml

version: '3.9'
services:
  postgresd:
    image: postgres:15
    environment:
      - POSTGRES_USER=kratos
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=kratos
    ports:
      - '${EXPOSED_POSTGRES_PORT:-5432}:5432'
    volumes:
      - ${POSTGRES_DATA:-./pg_data}:/var/lib/postgresql/data
    networks:
      - ory-network
  kratos-migrate:
    image: oryd/kratos:v1.2.0
    environment:
      - DSN=postgres://kratos:secret@postgresd:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4
    volumes:
      - type: bind
        source: ./kratos
        target: /etc/config/kratos
    command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
    restart: "no"
    networks:
      - ory-network
  kratos:
    depends_on:
      - kratos-migrate
    image: oryd/kratos:v1.2.0
    ports:
      - '4433:4433' # public
      - '4434:4434' # admin
    restart: "no"
    environment:
      - DSN=postgres://kratos:secret@postgresd:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4
      - LOG_LEVEL=trace
    command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
    stdin_open: true
    tty: true
    volumes:
      - type: bind
        source: ./kratos
        target: /etc/config/kratos
    networks:
      - ory-network
  mailslurper:
    image: oryd/mailslurper:latest-smtps
    ports:
      - '4436:4436'
      - '4437:4437'
    networks:
      - ory-network
  oathkeeper:
    image: oryd/oathkeeper:latest
    ports:
      - 8080:4455
      - 4456:4456
    command: serve proxy -c "/etc/config/oathkeeper/oathkeeper.yml"
    environment:
      - LOG_LEVEL=debug
    volumes:
      - ./oathkeeper:/etc/config/oathkeeper
    restart: "no"
    networks:
      - ory-network
  hydra-migrate:
    image: oryd/hydra:v2.2.0
    environment:
      - DSN=postgres://kratos:secret@postgresd:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
    volumes:
      - ./hydra:/etc/config/hydra
    command: migrate -c /etc/config/hydra/hydra.yml sql -e --yes
    restart: "no"
    networks:
      - ory-network
  hydra:
    image: oryd/hydra:v2.2.0
    depends_on:
      - hydra-migrate
    ports:
      - "4444:4444" # Public port
      - "4445:4445" # Admin port
      - "5555:5555" # Port for hydra token user
    command:
      # serve all --dangerous-force-http
      serve -c /etc/config/hydra/hydra.yml all --dev
    restart: "no"
    environment:
      - DSN=postgres://kratos:secret@postgresd:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
    volumes:
      - ./hydra:/etc/config/hydra
    networks:
      - ory-network
networks:
  ory-network:
    name: ory_default
    external: true


### Version

kratos: 1.2.0, hydra: 2.2.0

### On which operating system are you observing this issue?

None

### In which environment are you deploying?

None

### Additional Context

_No response_
EverettSummer commented 1 month ago

image This is the records in the hydra database, hydra_oauth2_authentication_session table

EverettSummer commented 1 month ago

Probably caused by the configuration of Krato, the version in the kratos.yml is 1.0.0 while the kratos image version is 1.2.0. After upgrading the version of kratos.yml, I no longer saw the null response.