Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkce documentation and examples. #2244

Open
2 tasks done
nelaaro opened this issue Aug 16, 2021 · 12 comments
Open
2 tasks done

pkce documentation and examples. #2244

nelaaro opened this issue Aug 16, 2021 · 12 comments

Comments

@nelaaro
Copy link

nelaaro commented Aug 16, 2021

Preflight Checklist

  • I agree to follow the Code of Conduct that this project adheres to.
  • I have searched the issue tracker for an issue that matches the one I want to file, without success.

Problem Description

Feature request for additional documentation and example in the implementation of dex and dex features.

PKCE support has been added to Dex in version 2.26. It would be great if there was a single document or example explaining how to implement it.

It should be listed as a feature that Dex provides clearly and unambiguously. I only found out about this because I searched through the issue list and read a tutorial describing how one might implement this using dex.

Proposed Solution

Create an example config that shows how to create a PKCE deployment. With complete explanations for the different options and how they work to implement PKCE.

There should also be an expanded example config that includes all the options available to configure dex.
One example of an option not documented except in the issue. allowedOrigins: ['*']

web:
  http: 0.0.0.0:5556
  allowedOrigins: ['*']

I think the PKCE implementation should be part of the default configuration set as it simplifies a number of implementation details and could make for more secure deployments. As it mitigates the leaking of the token to third-party in web application endpoints.

Alternatives Considered

Reading the code directly to understand all the available options and how to implement them.
Reading through all the issues to understand what options are available and how they are implemented.
Not a good way for new users to get to grips with how this is supposed to work.

Additional Information

PKCE has been recommended to replace implicit flow to reduce security risks associated with leaking the secret key.
This should become part of the examples and documentation implementation options that dex provides

I have been reading from these sites to better understand oauth and PKCE and dex. OAuth

@titlisgupta
Copy link

I am having same trouble, a documentation of PKCE with example would have been a great help.
@nelaaro are you able to configure DEX with pkce? I am facing some issues with code challenge method.

@0x113
Copy link

0x113 commented Jul 24, 2023

Any update on this?

@dweebo
Copy link

dweebo commented Sep 20, 2023

@HEllRZA do you know if PKCE config for dex is documented anywhere? We are also unsure how to configure it.

@HEllRZA
Copy link
Contributor

HEllRZA commented Sep 20, 2023

@dweebo
No, but I can try:

# The base path of dex and the external name of the OpenID Connect service.
# This is the canonical URL that all clients MUST use to refer to dex. If a
# path is provided, dex's HTTP service will listen at a non-root URL.
issuer: http://192.168.1.138:5556/dex

# The storage configuration determines where dex stores its state. Supported
# options include SQL flavors and Kubernetes third party resources.
#
# See the storage document at Documentation/storage.md for further information.
storage:
  type: sqlite3
  config:
    file: examples/dex.db

# Setup the client
staticClients:
  - id: example-app
    redirectURIs:
      - "https://op-test:60001/authz_cb"
      - "https://op-test:60001/authz_post"
    name: 'Example App'
    public: true

# Configuration for the HTTP endpoints.
web:
  http: 0.0.0.0:5556
  allowedOrigins: ['https://op-test:60001']

# Configure for PKCE flow
oauth2:
  responseTypes: ["code"]
  skipApprovalScreen: true

In this configuration the important things are

  • redirectURIs have to be set correctly. It is completely dependent on the way the client will be notified after the user has logged in.
  • allowedOrigins has to be set up correctly for SPA, else CORS errors occur.
  • responseType: ["code" ] is enough for PKCE
  • secret can be omitted if only PKCE is used. "public: true" is needed then.

Whether or not PKCE is actually used for the auth flow, is the decision of the client.

The client should:

  • call the dex/.well-known/openid-configuration to get all the endpoints and the code_challange_methods, which are "S256" and "plain". Plain should only be used for testing purposes, not for production.
  • create a random code_verifier. Then it is transformed to a code_challenge with a code_challange_method ("S256" or "plain").
  • Sending the code_challenge and the code_challenge_method to the /auth endpoint starts the PKCE flow.
  • Lots of redirects (and the user login) happen here
  • When the callback is called, Instead of getting the access_token (JWT) directly, the client gets a authorization_code in return.
  • The client now sends the authorization_code and the code_verifier to the '/token' endpoint and finally receives the access_token.

The problem really is, that many little things can go wrong. And in every type of application, things are different. E.g. for an iOS Apps, the redirectURI has to open the application, so the URL scheme has configured with iOS to allow opening the app. For SPAs CORS has to be configured. For Electron apps either an URL scheme, similar to iOS has to be used (but how does that work for Linux/Windows/MacOS), so a local server could to be started to get the callback ...

These documents helped me to understand PKCE:
https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-proof-key-for-code-exchange-pkce
https://blog.postman.com/pkce-oauth-how-to/
And of course the RFC: https://datatracker.ietf.org/doc/html/rfc7636

Disclaimer: I did not look in to this for a long time, I hope, everything i write is correct.

@dweebo
Copy link

dweebo commented Sep 21, 2023

@ HEllRZA thank you so much for taking the time to answer so thoroughly! You didn't have to and I really really appreciate it! I'll dig in today with this info 🤞

@xp-zr
Copy link

xp-zr commented Dec 6, 2023

@HEllRZA The example your provided is working! Thank you so much!

@thiDucTran
Copy link

but does responseTypes: ["code"] applies to all clients? including the non-public ones?

@HEllRZA
Copy link
Contributor

HEllRZA commented Dec 13, 2023

@thiDucTran
Yes, responseTypes apply to all clients. You can also use PKCE on non-public clients, however you will have to send the secret to get the access_token on the /token endpoint.
If you don't want to use PKCE on non-public clients, don't start with a code_challenge on auth and dex will start the default "Authorization Code Flow".

@tanqhnguyen
Copy link

tanqhnguyen commented Jan 2, 2024

I am following the example posted by HEIIRZA, but stumbling upon a weird issue (or I might make some obvious mistake here)

According to this if I don't specify code_challenge_method it is default to plain. However here is my request to authorize the user

http://localhost:5556/dex/auth?
code_challenge=1cae126fc0a97569eca760eac7f575324c805fe671d2d056dc26335158fcecc3&
code_challenge_method=S256&
client_id=log-me-in&
redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Fcallback&
scope=openid+profile+email&
response_type=code

and here is what being stored in the database. Even though the request specifically set the code_challenge_method to S256 but it's always stored as plain for some reason which makes the exchange fail because in the exchange request, I'm sending the code verifier which is the original value of the (hashed) code challenge.

d                         | u54xvojila3c7chtj6tmoynbf
connector_id              | google
code_challenge            | 1cae126fc0a97569eca760eac7f575324c805fe671d2d056dc26335158fcecc3
code_challenge_method     | plain

Not sure if I am missing anything here 🤔

@HEllRZA
Copy link
Contributor

HEllRZA commented Jan 2, 2024

I am following the example posted by HEIIRZA, but stumbling upon a weird issue (or I might make some obvious mistake here)

According to this if I don't specify code_challenge_method it is default to plain. However here is my request to authorize the user


http://localhost:5556/dex/auth?

code_challenge=1cae126fc0a97569eca760eac7f575324c805fe671d2d056dc26335158fcecc3&

code_challenge_method=S256&

client_id=log-me-in&

redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Fcallback&

scope=openid+profile+email&

response_type=code

and here is what being stored in the database. Even though the request specifically set the code_challenge_method to S256 but it's always stored as plain for some reason which makes the exchange fail because in the exchange request, I'm sending the code verifier which is the original value of the (hashed) code challenge.


d                         | u54xvojila3c7chtj6tmoynbf

connector_id              | google

code_challenge            | 1cae126fc0a97569eca760eac7f575324c805fe671d2d056dc26335158fcecc3

code_challenge_method     | plain

Not sure if I am missing anything here 🤔

Seems strange. I am on vacation, so I can't really dive into it.

However, I see two things. You are using google? There might be an issue with that, but I don't really remember. You could test with a local dex-user login instead.

I think, scope should be (url escaped) space-separated not + separated.

And a Question: Which of the various possible error messages do you get?

@tanqhnguyen
Copy link

tanqhnguyen commented Jan 2, 2024

You are using google?

Yes I'm using google

There might be an issue with that, but I don't really remember. You could test with a local dex-user login instead.

I can give it a try

I think, scope should be (url escaped) space-separated not + separated.

It is space separated, this is how I construct the URL

    const params = new URLSearchParams({
      code_challenge: codeChallenge,
      code_challenge_method: 'S256',
      client_id: 'log-me-in',
      redirect_uri: `${location.origin}/callback`,
      scope: 'openid profile email',
      response_type: 'code',
    })

    return `${baseUrl}/dex/auth?${params.toString()}`

And a Question: Which of the various possible error messages do you get?

When calling /auth end point if I specify a random code_challenge_method, I get Unsupported PKCE challenge error. I did this to make sure the method is parsed correctly and I don't have any typo

After that, when I do POST /token, it returns

{"error":"invalid_grant","error_description":"Invalid code_verifier."}

Here is how I construct the request

    const data = new URLSearchParams()
    data.append('code_verifier', codeVerifier)
    data.append('code_challenge_method', 'S256')
    data.append('code', code)
    data.append('grant_type', 'authorization_code')
    data.append('client_id', 'log-me-in')
    data.append('redirect_uri', `${location.origin}/callback`)

    const res = await fetch(`${baseUrl}/dex/token`, {
      method: 'POST',
      body: data,
    })

It only works if I set the code_challenge_method to plain 🤔

Seems strange. I am on vacation, so I can't really dive into it.

Totally understandable, that's why I didn't tag anyone in particular :)

@tanqhnguyen
Copy link

tanqhnguyen commented Jan 2, 2024

Actually, I found the problem after reading more into how the code challenge is generated, I'm missing the base64 url encoding part.

I will play around with this a bit more and update later :)

Updated: It was a combination of wrong/mismatch encoding between javascript and the golang part. And the reason why I saw plain instead of S256 was because of a caching problem.

mougams added a commit to mougams/haproxy-spoe-auth that referenced this issue Jun 21, 2024
The client secret is not required in PKCE authentication
mode.
See dexidp/dex#2244 for more info
mougams added a commit to criteo/haproxy-spoe-auth that referenced this issue Jun 21, 2024
The client secret is not required in PKCE authentication
mode.
See dexidp/dex#2244 for more info
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants