Mc-Auth-com/Mc-Auth

PKCE - CSRF Protection

Opened this issue · 1 comments

PKCE is an extension to the OAuth 2.0 spec designed to combat CSRF (cross-site remote forgery). Originally designed for mobile/single page apps its becoming more and more common in OAuth systems as it provides good additional security.
It's broken down into two steps. (Authorize setup and token verify).

Authorize:
Before the OAuth flow starts, the initiating server generates a random (secure) string (between 43-128 characters, A-Z, a-z, 0-9 and select punctuation). This generated hash is called the "code verifier". The app then hashes the code verifier using SHA256 (plaintext can be used, but it pretty much defeats the purpose) to generate a code challange. The original code verifier is stored on the origin server, not the mc-auth server. (this is the "code challenge"). The code challenge is then base64 url encoded (this could be different from default base64 encode depending on the language being used, idk javascript too well)

The authorize request would now have these additional arguments (for a PKCE request):
code_challange=BASE64_URLENCODED_CODE_CHALLENGE&code_challenge_method=S256

Code challenge method is used to allow origin servers to specify if the challenge has been hashed using SHA256 or if its just plain. (Should always be either: S256 or plain as in spec).

The code challenge used for the authorization would then be stored with generated access code (however the state is currently stored).

Token Verify:
When the access code is being exchanged for a token (where the state is validated) the PKCE should also be validated (if provided). An additional argument code_verifier is sent to the token route. This is the original string used to generate the hash from, meaning the server is able to validate it simply by hashing the provided verifier using the same method used originally (as specified by code_challenge_method) and then comparing it to the stored code_challenge.

Psudocode:

Origin server authorization:


# Before the authorization redirect, generate the code challenge
code_verifier = randomString()
code_challenge = sha256_hash(code_verifier)

# The code verifier is stored to be re-sent in the authorize request
somehow_store_this(code_verifier)

# Add required arguments to authorization redirect URI
redirect = redirect + "&code_challenge=" + base64_urlencode(code_challenge) + "&code_challenge_method=S256"

MC-Auth authorization flow:


# Generate the access code that is returned to the application, storing
# the provided code challenge and code_challenge_method with the code
# (for the token route to verify the state and code_challenge etc)

somehow_store_this(code_challenge)
somehow_store_this(code_challenge_method)

# Redirect back to origin, no additional arguments required.

Origin server token exchange:


# Get the original code_verifier from storage
code_verifier = get_from_storage()

# Add the original verifier as an argument to the token exchange route
# alongside the other arguments such as the code, client_secret etc.
body_arguments["code_verifier"] = code_verifier

# Send the token request
sendRequest("/token", body_arguments)

MC-Auth token exchange:


# Get the original provided code_challenge and the code_code_challenge_method that
# was used to generate the challenge from the authorization request.
original_code_challenge = get_from_storage()
original_code_challenge_method = get_from_storage()

# Get the provided code verifier argument from POST body.
code_verifier = getArguments["code_verifier"]

# If the code was generated using SHA256 hash, we should hash the provided verifier. If not
# just compare the original provided verifier with the now provided verifier (way less secure).
expected = original_code_challenge_method === "R256" ? sha256_hash(code_verifier) : code_verifier;

# Ensure the verifier is as expected.
if (original_code_challenge !== expected) {
   error("invalid code challenge for verifier");
}

Additional info:

Very detailed - I think I understand how it is supposed to work