2

Pardon if this is meant to be obvious. As I understand it, the point of PKCE is to mitigate client-side credential hijacking by giving the auth server a way to distinguish legitimate client-side requests using a verification chain. This in turn works by using ephemeral session data that a malicious user cannot access or mimic.

For the first PKCE request beginning at login I see how this works perfectly. For the next resource request, the logged-in client must generate a new code challenge pair and lodge the code challenge with the auth server at the /authorize endpoint.

In this case, the auth server will use the access token to authenticate the client request, no? If that's the case, then what stops a malicious user from using a hijacked token to illegitimately obtain access tokens, this time by mimicking the entire pkce flow beginning at /authorize?

I feel like I'm missing something basic--- if the point of PKCE is to distinguish such requests then surely either the auth server is tracking something else specific to the session, or else subsequent requests to lodge new code challenges by the client must also be validated with additional ephemeral log-in session data (the code challenge pair itself doesn't cut it, as it can be defined independently of a client session).

New contributor
ithmath is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
5
  • "PKCE" and "login" are unrelated; one is part of OAuth, which is for authorization (to resources and/or identity), and the other is a step in authentication. Using the terms at oauth.com/oauth2-servers/pkce, what are you confused about? Is is that you understand the Authorization Request but not the following Authorization Code Exchange? Or that you expect the client to perform two complete PKCE exchanges, one for login (presumably via OIDC) and another for resource authorization? Or something else? Commented yesterday
  • I appreciate that "PKCE" and "login" are different but I disagree that they have nothing to do with one another. Login is the first authentication step in the PKCE authorization flow. My question is as stated in the comment-- understanding the mechanics of how the code challenge pair is regenerated for logged-in users. Commented yesterday
  • Login is orthogonal to PKCE and OAuth. You may already be logged into the OAuth server (e.g. Google) before the app you're using begins OAuth, or already logged into the app but need to log into the resource provider's OAuth server when the app triggers an OAuth request (e.g. to grant Slack access to your Google calendar), or already logged into both (and just need to authorize, no logging in at all), or logged into neither (and using OAuth for SSO via OIDC for login in the app, but needing to log in to the OAuth server as well when the request arrives). Commented yesterday
  • Fine, what I'm essentially trying to understand is this: PKCE rests on creating a secret with the authorization server (really, lodging some way to verify the secret) and later passing the secret in with the intended resource request. If this lodgement requires the use of a vulnerable token stored on browser, then I'm trying to understand what prevents a malicious attacker from hijacking this token and emulating the pkce flow himself to generate new resource tokens. If the generated secrets are not dependent on the session then how does the auth server tell these requests apart? Commented yesterday
  • @ithmath: You really need to look up how OAuth works and use the correct terminology. Right now, you're throwing around all kinds of terms and descriptions which make no sense. For example, PKCE has nothing to do with resource requests (it's only used between the client and the authorization server), and there are no “resource tokens” in OAuth (you probably mean access tokens). OAuth also doesn't have “sessions” or a “log-in” feature. And I'm not sure what you mean by “logment”. Commented yesterday

2 Answers 2

1

I suspect your confusion is in how OAuth itself works, rather than anything about PKCE in particular. The purpose of OAuth is to allow an app (the OAuth client) to access identity information or resources from some other app/service (the OAuth server). Critically the OAuth app never sees any user credential, or access token, that the OAuth server uses for itself. That's the central point of OAuth; the ability for an app to use e.g. stuff in your Google account without getting general access to your Google account.

As such, a lot of your questions don't make much sense. I've tried to address them all below, with explanations and guesses as to where your confusion is and how things really work. Let me know (in a comment, or by editing the question) if something is still not adding up for you. It might help, in cases like that, to lay out your understanding of what's happening / how things work in considerable detail.


the point of PKCE is to mitigate client-side credential hijacking

Sort of, but perhaps not in the way you mean. The point of PKCE is to mitigate theft of the authorization code, the short-lived secret that is returned from the Authorization Request (assuming authorization is granted) and then transmitted during the Authorization Code Exchange. This is technically a credential, but not in the sense of a password, access token, API key, etc; it has only one purpose, is single-use, and expires immediately whether or not it's used.

The typical attack vector for stealing authorization codes is to have an app that registers the URI scheme for a different app (e.g. a malicious app registers for Slack's URI scheme) and thus when the OAuth server tells your device to redirect to slack:...code=XXXX, the wrong app receives the code (and can immediately exchange it for an access token). PKCE mitigates this by requiring that the trusted app generate a "code challenge" secret, which the OAuth server associates with the authorization code that it generates and returns (but does not include the code challenge secret in the response).

For the next resource request, the logged-in client must generate a new code challenge pair and lodge the code challenge with the auth server at the /authorize endpoint

If you're using OAuth/OIDC for login, why is a logged-in client needing to contact (the same) OAuth server again anyhow; it should have received an access token / authorized resource access already? If you mean a different OAuth server, then sure, the app (OAuth client) will need to generate a new PKCE challenge, but that has nothing to do with the prior OAuth activity or the app's logged-in status.

In this case, the auth server will use the access token to authenticate the client request, no?

Assuming by "auth server" you mean OAuth server, "no" indeed. The use case for OAuth is when you don't have an access token (or at least, not one for the resource you're trying to access); access tokens often come to your app from OAuth servers, but never flow from your app to the OAuth server.

what stops a malicious user from using a hijacked token ...

If by "hijacked token" you mean "hijacked access token", nothing (or at least, nothing in OAuth; there can be other measures sometimes though none of them are very reliable). Once an OAuth server vends you (app) an access token, anybody could use it; it is your responsibility to keep it out of malicious hands. The OAuth standard has some opinions about things like how long the token should last and whether you should be able to refresh it without full re-authentication, but fundamentally, protecting the access token once it is sent to the OAuth client (app) is always the app's responsibility.

... using a hijacked token to illegitimately obtain access tokens

Again, if the hijacked token is an access token that was issued by the OAuth server, the thing stopping this from working is that the OAuth server is not looking for and does not care whether or not you send back the access token it sent you. The protection against a malicious client getting access tokens from the OAuth server is the same things it always is:

  • [user] don't run malicious apps
  • [user] if an OAuth server asks about authorization for a malicious/unexpected app then don't grant it
  • [OAuth servers] clearly indicate which app is requesting what authorization (so the user can decide whether or not to approve)
  • [app authors] specify a minimal scope of acceptable redirect URIs
  • [app authors] use a client secret (where possible) and don't expose it
  • [app authors] use the State secret and verify that it is correct and expected whenever you get a request to the OAuth redirection target (technically this is about preventing a malicious app from authorizing your app to a malicious account, but that basically counts since a malicious app will already have access to the malicious account)
  • [OAuth servers] verify the authorization code and challenge (and client secret, where present) correctly before returning the access token
  • [OAuth servers] don't redirect (send the authorization code) to URIs not explicitly trusted by the client

That's multiple responsibilities on every party, user included. OAuth is complicated and hard! If done right, it's secure, but don't use it unless you have a good reason to. People get confused and make mistakes about it a lot, and a lot of client (and some server) implementations are buggy, often in security-impacting ways.

requests to lodge new code challenges by the client must also be validated with additional ephemeral log-in session data

Authorization requests via OAuth always require that the OAuth server have some idea who you (the user) are, so they know what things you're allowed to authorize access to, but that information never comes from the OAuth client. It typically comes from the browser, in the form of a first-party session token. For example, if an app is using Google as your OAuth server - whether for OIDC or resource access - then the app never has access to your Google session, but your browser does; if your browser isn't logged into Google when the OAuth request starts, then Google will first demand that you log in so it knows what you can authorize for the app.

To be clear, since it seems like this might be the crux: if that first-party (only ever used on e.g. *.google.com domains) session token is stolen, no part of OAuth (including PKCE) can do anything about it, and since that token is never exposed to any OAuth clients anyhow (since they're third-party), no OAuth feature could even in theory protect (or expose) it.

[from comment] If this lodgement requires the use of a vulnerable token stored on browser, then I'm trying to understand what prevents a malicious attacker from hijacking this token

If the token you refer to is the first-party session token for the OAuth server, then "what prevents a malicious attacker from hijacking this token" is just... writing a secure first-party web application. The token needs to be securely generated with high entropy so it can't be guessed, transmitted exclusively over secure connections, stored securely on the client, not exposed via XSS or similar, and so on. The browser will do the rest, via Same-Origin Policy preventing third-party sites from reading secrets across origins.

If the token you're referring to is one that the OAuth server previously returned to the app (OAuth client), then that token isn't used for future OAuth requests at all. Also, it might be stored in the browser (if the app is web-based) but often it isn't; it might be stored in a mobile or desktop app, or sometimes in the back-end server of an app, or (typical for OIDC) it would be used to generate an app-specific session token between the app's front-end (web or otherwise) and its back-end and then discarded.

1
  • Thanks a lot for all the detail, I really appreciate it. I think I have a sharper idea of what PKCE is attempting to and NOT trying (or capable) to protect. It clarifies a lot. I understand what OAuth is generally for this context (the confusion concerning sending a token to authorize really had to do with how auth had any ability to discern "validated" secrets or not), but I see now that this is not the point of PCKE and so it's not really relevant. Commented yesterday
1

I believe you misunderstand the purpose of PKCE. This technique does not protect client credentials in any way. In fact, it's strictly meant for public clients which typically do not have credentials at all. There are also no “sessions” in OAuth, and clients cannot “log in”. Authentication happens on a per-request basis. For example, if a confidential client like a web application running on a server has an API key for authentication, then it needs to include the API key in every request to the authorization server.

PKCE is intended to solve a single problem: When a public OAuth client like a mobile app obtains an authorization code, then it often uses an insecure redirection URL like a deep link with a custom URL scheme (e.g., some_app://oauth_handler). In contrast to HTTPS URLs which ensure end-to-end encryption between the client and the authorization server, deep links to apps do not necessarily protect against traffic interception. For example, a malicious app which is installed on the same device might be able to also register itself as the handler for the custom scheme (myapp). When the authorization server sends the authorization code to the deep link, the malicious app can intercept this code and abuse it to obtain an access token.

Note this problem only exists for public clients which cannot securely store credentials and therefore typically make unauthenticated requests to the authorization server. A confidential client doesn't have this issue, because the authorization server forces those clients to authenticate with every request. So an attacker who only has the code but not the client credentials cannot use the code.

For public clients, PKCE solves the code interception problem as follows:

  • Before sending an Authorization Request, the client generates a random secret (the code verifier).
  • This secret (or a hash of it) is included in the Authorization Request and sent to the TLS-protected authorization endpoint. Note that due to the use of TLS, an attacker cannot intercept this secret.
  • The server generates an authorization code and stores it together with the secret. It sends the code to the client, possibly over an insecure channel.
  • When the client makes an Access Token Request, it must send both the authorization code and the secret associated with the code. The server will only accept the code if the secret in the request matches the previously stored secret.

This means: Even if an attacker manages to intercept the authorization code, they cannot get an access token without knowing the associated secret.

The designers of PKCE do not make any other promises. For example, if an attacker has compromised the client to such an extend that they can obtain the secret, then PKCE doesn't help. It also doesn't change the fact that a public client can be impersonated much more easily than a confidential client with well-protected credentials.

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.