How do I use Proof Key for Code Exchange (PKCE)
If your integration is a mobile app or a WordPress plugin, you probably have a public client type and will be using Proof Key for Code Exchange (PKCE) for your authentication. This variant flow for OAuth 2 is used when your client secret cannot be kept secure as described in our documentation. This guide will provide a detailed walkthrough on how to implement PKCE in your application since many libraries for OAuth 2 do not provide out of the box support for this kind of flow.
Looking for the standard OAuth 2 flow instead? We provide a walkthrough in our documentation.
The PKCE or public OAuth 2 flow is very similar to the standard flow. This guide will cover the four steps and point out the differences along the way.
- Create a code challenge and verifier
- Build an Authorization URL
- Get an Authorization Code
- Trade for Access Token
- Refresh your Access Token
Just need the endpoints? They’re exactly the same as the standard OAuth 2 flow.
Authorize URL:
https://auth.aweber.com/oauth2/authorize
Access Token / Refresh Token URL:
https://auth.aweber.com/oauth2/token
Step 1: Create a Code Challenge and Verifier
Rather than using a client secret like in the standard OAuth 2 flow, we’ll be using a code challenge and code verifier for this flow. The verifier is a random ASCII string and the challenge is a SHA256 hash of that verifier. These sound complex if you’ve never used any hashing functions before, but the good news is that most programming languages will be able to help you with built in functions.
First, generate the verifier. Make sure to use your language’s cryptographically secure version of random generation if you have the option. For example in Python use os.urandom() instead of the usual math.random(). For PHP the random_bytes() function is acceptable. Once you have your method chosen using following the documentation to generate a string between 43 and 128 characters long. Then base64 encode it, using a URL-safe base64 encoding or replacing the + and / characters as appropriate for your language. Here are examples in PHP and Python:
Python:
verifier_bytes = os.urandom(32)
code_verifier = base64.urlsafe_b64encode(verifier_bytes).rstrip(b'=')
PHP:
$verifier_bytes = random_bytes(64);
$code_verifier = rtrim(strtr(base64_encode($verifier_bytes), "+/", "-_"), "=");
Now that you have your verifier you need to hash it. Most languages should help you out here as well. Python has hashlib and PHP has a hash function, if you’re using another language you should check the documentation. Use your hash function as documented to create a SHA256 hash. Important note for PHP users: Make sure to set the “raw_output” boolean to true or you’ll get an invalid_grant error. Then base64 encode as before. Here are examples in both PHP and Python:
Python:
challenge_bytes = hashlib.sha256(code_verifier).digest()
code_challenge = base64.urlsafe_b64encode(challenge_bytes).rstrip(b'=')
PHP:
// Very important, "raw_output" must be set to true or the challenge
// will not match the verifier.
$challenge_bytes = hash("sha256", $code_verifier, true);
$code_challenge = rtrim(strtr(base64_encode($challenge_bytes), "+/", "-_"), "=");
Now that you have both your challenge and verifier you have completed the hardest part of the PKCE flow!
Step 2: Build an Authorization URL
First we need to put the required information together that will tell AWeber you have a new AWeber customer authorizing your integration. This is a URL that will be used to obtain an authorization code showing the customer has approved your integration.
This step is almost the same as the standard OAuth 2 flow. The difference is that when using PKCE you need to have an additional parameter for the code challenge created in step 1, and a second new parameter containing the method used to create the code challenge. Please review the example below:
https://auth.aweber.com/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_CALLBACK&scope=YOUR_REQUESTED_SCOPES&state=STATE_TOKEN&code_challenge=YOUR_CHALLENGE&code_challenge_method=S256
The components of this URL are as follows:
- The base URL is provided by AWeber and connects you to our servers.
- response_type tells AWeber what you want us to send back. For this step you should always use “code” since you want an authorization code.
- client_id is the Client ID listed in your developer account. It uniquely identifies your integration to AWeber.
- redirect_uri is your callback. This is where the user-agent (in most cases the customer’s browser) will be sent after the customer clicks “authorize”. This should be a uri that your application can read because that’s where we’ll provide our response. When you provide your callback, make sure it’s the same one you specified when creating your integration. If you do not have a callback, you can use urn:ietf:wg:oauth:2.0:oob for our out of band alternative.
- scope is a list of space separated permissions your integration requires. Check out our guide to scopes for a list. To change permissions later, all customers will need to repeat the authorization process. Please ensure you have the ones you need.
- state is a token you provide to AWeber for CSRF protection. You can also use this token as a session token to preserve user data between the auth steps, providing a better experience for your users. We pass this data back to you later and you can check that it matches the value you sent. A random string or a hash of a session cookie is acceptable here, it just needs to be something you know but a potential attacker doesn’t and it should change for each new user.
- code_challenge is the hashed challenge from step one.
- code_challenge_method tells AWeber how you hashed your challenge. We only accept SHA256 challenges, so you can just put “S256” here.
Step 3: Get an Authorization Code
This step is also just like the standard OAuth 2 flow. Using the authorization URL you generated in Step 2, create a button or hyperlink that says “Click to connect to AWeber” or something similar.
The link you provide will lead customers to AWeber’s authorization screen. This screen outlines your integration, the access being requested, and an area for customers to provide their username and password. Customers will review the screen, enter their login credentials, and click authorize.
Clicking authorize (after entering valid credentials) redirects the user to the callback URI you specified in Step 1. In the query string of the redirect will be a query parameter containing your authorization code. If you provided a state token in step 1, that is sent back as a second query parameter.
For example, if the redirect_uri you provided was https://app.example.com/auth we would redirect the AWeber customer’s browser to:
https://app.example.com/auth?code=YOUR_NEW_AUTH_CODE&state=STATE_YOU_PROVIDED
You should collect the query parameters from the URI. Please verify the state token sent to you is the same as the one you gave us. If everything is valid, save the code parameter for the next step. Your chosen library may handle verification of the state token for you.
If you specified urn:ietf:wg:oauth:2.0:oob as your redirect_uri, the code will be displayed in a page for users to copy and paste into your application instead.
Step 4: Trade for an Access Token
Once you have obtained your authorization code the only thing left to do is trade it for an access token that can be used to make calls to AWeber’s API. This is a POST request to https://auth.aweber.com/oauth2/token that is very similar to the one used in the standard OAuth 2 flow. Since you won’t have a client secret, simply include the client ID in your request body rather than using HTTP Basic authentication. In addition, include your code verifier (the unhashed random string) from step 1. Here’s what the POST request will look like:
POST /oauth2/token HTTP/1.1
Host: auth.aweber.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=YOUR_AUTHORIZATION_CODE&client_id=YOUR_CLIENT_ID&code_verifier=YOUR_VERIFIER
- grant_type is a parameter that tells us if you’re getting a brand new access token or refreshing one. For a new token always pick authorization_code.
- code is the authorization code you obtained in step 2.
- client_id is the client ID listed in your developer account that you used to obtain the authorization code
- code_verifier is the unhashed random string from step 1
When processing the POST request, AWeber’s servers will compute the SHA256 hash of your verifier. If it matches the challenge you provided in step 2 then the request for an access token will be considered valid and you will get an access token and refresh token back in our response. If they don’t match, an error will be returned instead.
Step 5: Refresh your Access Token
AWeber’s access tokens expire after a while and will require refreshing to continue making API calls. You’ll know it’s time to refresh when you start receiving 401 Unauthorized errors. This step works the same as the standard OAuth 2 flow except you omit the client_secret and simply include the client_id in the request body, much like in the request for access tokens. The POST looks like this:
POST /oauth2/token HTTP/1.1
Host: auth.aweber.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID
Having Trouble?
Here’s a few things to check if you’re running into problems with PKCE. The errors fall into a few broad categories depending on what the error and description are, so please reference the list below for your particular error.
If the error is “invalid_grant” with the description “invalid code”:
- Make sure your code challenge and verifier do not have + or / characters in them. This is usually a mistake in the URL encoding. Use a URL safe base64 encode or make sure to swap all + and / characters for - and _ respectively.
- If the code verifier and challenge do not match this error can appear as well. Make sure you’ve implemented the hashing correctly following the documentation for your language. If using PHP’s hash function be sure to set raw_output to true.
If the error is “invalid_request” with the description “invalid code”:
- Make sure you’re using a fresh authorization code with each request and that it’s copied into your request correctly.
- Make sure you’re using the same client ID in both the authorization code request and the token request.
If you’re still having trouble please send details to us at api@aweber.com and we’ll be happy to help you out. Please include your client ID or your app’s name and developer account email so we can look up your request.