oauthoauth-2.0rails-apidoorkeeperpkce

Do we really need client_secret to get access_token on PKCE flow?


I am building 2 apps; a front-end, and a back-end.

The back-end will be built using Rails API + Doorkeeper Gem (oauth2 provider) while the front-end will be built using React Native.

Currently, I am using "Client Credentials Grant Flow" which works just fine at the moment. But after researching for a while, this flow shouldn't be used in a client-only app as it exposes the client_secret if ever someone decompiles the app.

I have also read about "Implicit Grant Flow" which only requires client_id. But this flow seems old now?

And according to this: https://auth0.com/docs/api-auth/which-oauth-flow-to-use#is-the-client-a-single-page-app-

It is recommending to use "Authorization Code Grant with PKCE" over "Implicit Grant Flow". I am able to make it work but the problem is that it still needs the client_secret in order to get an access_token, is this how it should be?

Here is the sample flow I am doing:

curl -X POST 'http://localhost:3000/oauth/authorize?client_id=xxxx&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=public&code_challenge=testchallenge&code_challenge_method=plain'

This will give me the following response:

{
    "status": "redirect",
    "redirect_uri": {
        "action": "show",
        "code": "8quZ-EAiKKG2EKnQiSYs3xeFRCgsIwcTbaWNdjnpyFg"
    }
}

And then I will use the code above to get an access token using the request below:

curl -i http://localhost:3000/oauth/token \
  -F grant_type="authorization_code" \
  -F client_id="xxxx" \
  -F client_secret="xxxx" \
  -F code="8quZ-EAiKKG2EKnQiSYs3xeFRCgsIwcTbaWNdjnpyFg" \
  -F redirect_uri="urn:ietf:wg:oauth:2.0:oob" \
  -F code_verifier="testchallenge"

Now here is the problem, in order to exchange code to an access_token I will still be needing the client_secret. How is it different from "Client Credentials Grant Flow" if both will just expose my client_secret?

The above command will return the following:

{
    "access_token": "nQoorBqLxQH4qFpmlx3mGG6Cd_TfX4d3L3gAGOTwrFs",
    "token_type": "Bearer",
    "expires_in": 7200,
    "scope": "public",
    "created_at": 1595517643
}

If I don't include the client_secret in the request here is the response:

{
    "error": "invalid_client",
    "error_description": "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method."
}

So here are my questions:

  1. Do we really need client_secret to get access_token on PKCE flow?
  2. Why is it recommended to use "PKCE Flow" if it will just expose the client_secret?
  3. How is it different from "Client Credentials Grant Flow" which also exposes the client_secret?

Doorkeeper PKCE documentation: https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-PKCE-flow


Solution

    1. Do we really need client_secret to get access_token on PKCE flow?

    It depends. Originally PKCE was introduced to protect public clients (a client which cannot protect a secret). But recently, with practices, PKCE became a recommendation for authorization code grant (source)

    2.1.1. Authorization Code Grant

    Clients MUST prevent injection (replay) of authorization codes into
    the authorization response by attackers. The use of PKCE [RFC7636]
    is RECOMMENDED to this end. The OpenID Connect "nonce" parameter and ID Token Claim [OpenID] MAY be used as well. The PKCE challenge or
    OpenID Connect "nonce" MUST be transaction-specific and securely
    bound to the client and the user agent in which the transaction was
    started.

    Note: although PKCE so far was designed as a mechanism to protect
    native apps, this advice applies to all kinds of OAuth clients,
    including web applications.

    1. Why is it recommended to use "PKCE Flow" if it will just expose the client_secret?

    In short, to avoid authorization code replay attacks (spec - introduction). And this happens inside end user's device and not in the transmission of data. TLS is mandatory for OAuth 2.0 token request.

    1. How is it different from "Client Credentials Grant Flow" which also exposes the client_secret?

    No grant will expose credentials as token requests are done via TLS.

    I think in your case, the client is a confidential client (spec - client types). So I would recommend to check this aspect in authorization server.