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.15k stars 956 forks source link

Active session after logout #2921

Closed drev74 closed 1 year ago

drev74 commented 1 year ago

Preflight checklist

Describe the bug

After clicking a logout button in Vue3 TS client, my browser session is still active. This breaks an auth logic, because my client is still signed in after being signed out.

Reproducing the bug

 "dependencies": {
    "@ory/client": "^1.0.1",
    "vue": "^3.2.41"
  },
const config = new Configuration({
  basePath: import.meta.env.VITE_KRATOS_API_URL,
  baseOptions: {
    withCredentials: true,
    timeout: 10000, // 10 sec
  },
});

const ory = {
  identity: new IdentityApi(config),
  frontend: new FrontendApi(config),
  oauth2: new OAuth2Api(config),
};

const session: Ref<Session | null> = ref(null);

const logoutURL = ref('');
...
onMounted(() =>
  // Fetch the session directly from Ory
  ory.frontend
    .toSession()
    .then(({ data }) => {
      session.value = data;
    })
    .catch((err) => {
      console.log(err);
      // If the user is logged in, we want to show a logout link!
      ory.frontend.createBrowserLogoutFlow().then(({ data }) => {
        logoutURL.value = data.logout_url + `?return_to=${APPHOME}`;
      });
    })
);

Relevant log output

session:Reactive
id:"73dfec8a-1d22-4276-a89c-a7b58e105077"
active:true <<------------------------------------------------------    ACTIVE SESSION !
expires_at:"2022-12-02T17:53:41.658277Z"
authenticated_at:"2022-12-01T17:53:41.658277Z"
authenticator_assurance_level:"aal1"
authentication_methods:Array[1]
issued_at:"2022-12-01T17:53:41.658277Z"
identity:Object
created_at:"2022-12-01T16:54:22.202059Z"
id:"58638b4c-843a-443b-a250-cd48eb748494"
metadata_public:null
recovery_addresses:Array[1]
schema_id:"preset://email"
schema_url:"http://localhost:4433/schemas/cHJlc2V0Oi8vZW1haWw"
state:"active"
state_changed_at:"2022-12-01T16:54:22.197581Z"
traits:Object
updated_at:"2022-12-01T16:54:22.202059Z"
verifiable_addresses:Array[1]
devices:Array[1]
0:Object
id:"9904a7d0-de1f-4931-9878-54d3a281b0fc"
ip_address:"172.21.0.1:57590"
location:""
user_agent:"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"

Relevant configuration

version: v0.10.1

dsn: memory

serve:
  public:
    base_url: http://localhost:4433/
    cors:
      enabled: true
      allowed_origins:
        - http://localhost:3000
      allowed_methods:
        - GET
        - OPTIONS
        - PUT
        - PATCH
        - DELETE
  admin:
    base_url: http://kratos:4434/

selfservice:
  default_browser_return_url: http://localhost:4455/
  allowed_return_urls:
    - http://localhost:3000
    - http://localhost:4455

  methods:
    password:
      enabled: true

  flows:
    error:
      ui_url: http://localhost:4455/error

    settings:
      ui_url: http://localhost:4455/settings
      privileged_session_max_age: 15m

    recovery:
      enabled: true
      ui_url: http://localhost:4455/recovery

    verification:
      enabled: true
      ui_url: http://localhost:4455/verification
      after:
        default_browser_return_url: http://localhost:4455/

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

    login:
      ui_url: http://localhost:4455/login
      lifespan: 10m

    registration:
      lifespan: 10m
      ui_url: http://localhost:4455/registration
      after:
        password:
          hooks:
            - hook: session

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: preset://email
  schemas:
    - id: preset://email
      url: file:///etc/config/kratos/identity.schema.json

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

Version

0.11.0

On which operating system are you observing this issue?

Linux

In which environment are you deploying?

Docker Compose

Additional Context

POSTGRES_VER=15.1-alpine
KRATOS_VER=v0.11.0 KRATOS_UI_VER=v0.11.0-alpha.0.pre.2 OATHKEEPER_VER=v0.40.0 KETO_VER=v0.10.0

drev74 commented 1 year ago

@vinckr Hi could you pls take a look :eyes:

ntheile commented 1 year ago

I am also having issues revoking if I have multiple sessions.

For example, I create two sessions by logging in twice, then I try to revoke the second session. It does not properly revoke. It actually revokes the wrong session (session 1) instead of (session 2).

@vinckr any ideas if this is related to the bug above?

Code to reproduce:

  it("revoke a user's second session only", async () => {
    // Session 1
    const session1 = await authService.login(user) // kratosPublic.updateLoginFlow
    const session1Token = session1.sessionToken

    // Session 2
    const session2 = await authService.login(user) // kratosPublic.updateLoginFlow
    const session2Token = session2.sessionToken

    // Session Details
    //  * caveat, you need to have at least 2 active sessions for 'listMySessions' to work properly
    //  if you only have 1 active session the data will come back null
    const session1Details = await kratosPublic.listMySessions({
      xSessionToken: session1Token,
    })
    const session1Id = session1Details.data[0].id
    const session2Details = await kratosPublic.listMySessions({
      xSessionToken: session2Token,
    })
    const session2Id = session2Details.data[0].id

    // Revoke Session 2
    await kratosPublic.disableMySession({
      id: session2Id,
      xSessionToken: session2Token,
    })

    // Check that session 2 was revoked
    const activeSessions = await kratosAdmin.listIdentitySessions({
      id: session2.kratosUserId,
      active: true,
    })

    const isSession1Revoked = activeSessions.data.find((s) => s.id === session1Id)
    const isSession2Revoked = activeSessions.data.find((s) => s.id === session2Id)
    expect(isSession1Revoked).toBeDefined() // session1Id should be in the list
    expect(isSession2Revoked).toBeUndefined() // session2Id should NOT be in the list

    // * validateKratosToken has a weird bug with multiple sessions
    //  it throws an error on session1 and thinks session2 is valid
    //  this is the opposite of what should happen
    const isSession1Valid = await kratosPublic.toSession({ xSessionToken: session1Token })
    const isSession2Valid = await kratosPublic.toSession({ xSessionToken: session2Token })
    expect(isSession1Valid).toBeDefined() // * BUG? this should be valid (but its not)
    expect(isSession2Valid).toBeInstanceOf(KratosError) // * BUG? this should be invalid (but its valid, and its the wrong sessionId, it returns session1's Id)
  })

Versions: @ory/client": "^1.1.0 oryd/kratos:v0.10.1

kmherrmann commented 1 year ago

Please try this with kratos 1.0 and reopen if this is still an issue - we struggle with reproducing it. thanks!