Handling Refresh Tokens in the OAuth 2.0 Authorization Code Flow with PKCE with Flask
Tags: python, twitter, oauth2, showdev
Published: 2022-06-17 20:42:05

---
title: Handling Refresh Tokens in the OAuth 2.0 Authorization Code Flow with PKCE with Flask
published: true
description: 
tags: #python #twitter #oauth2 #showdev
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
---
We recently released [OAuth 2.0 Authorization Code Flow with PKCE](https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code) for use with most of our v2 endpoints. As a backend-focused Python developer, I struggled with the flow since much of the code I like to write focuses on automation tasks. 

This tutorial will walk you through critical concepts related to refresh tokens using v2 of the Twitter API. If you don’t already have access to the Twitter API, [you can sign up for an account.](http://t.co/signup) You will also need to have OAuth 2.0 turned on in your App’s settings.

## Tokens are only valid for two hours 

I was used to working with OAuth 1.0a, where keys and tokens are available for use until they are revoked. This is not the case with [OAuth 2.0 Authorization Code Flow with PKCE](https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code). Access Tokens generated using this flow are only valid for 2 hours. 

## Refresh tokens

Inside the token object generated from the consent flow is a [refresh token](https://oauth.net/2/refresh-tokens/#:~:text=An OAuth Refresh Token is,scope of the original grant.). A refresh token allows an application to obtain a new access token without prompting the user to log in again.

To generate a refresh token, you must set a scope for offline access. If I was using the manage Tweets endpoint and I wanted to Tweet on behalf of a user every six months, I’d use the following line of Python code:

```python
scopes = ["tweet.read", "users.read", "tweet.write", "offline.access"]
```

## How long are refresh tokens valid for?

Refresh tokens stay valid for six months, so you will want to refresh them at least that often or more regularly. 

## Generating tokens

To generate a token using OAuth 2.0 Authorization Code Flow with PKCE you can use a method similar to [this example](https://github.com/twitterdev/Twitter-API-v2-sample-code/blob/main/Bookmarks-lookup/bookmarks_lookup.py) that allows you to look up an authenticated user’s bookmarks. Since the authenticated user will need to log in directly, and you will need to parse the response, I found creating a [Flask](https://flask.palletsprojects.com/en/2.1.x/) application allowed for a more automated process. 

The following code will generate a token and save the token into a dictionary.

You will be working with the following libraries:

- `requests` for making HTTP requests

- `requests_oauthlib` for working with OAuth 2.0

- `os` for parsing environment variables and creating random strings 

- `re`, `base64` and `hashlib` to create the code challenge and code verifier

- `flask` for creating a web framework

If you don’t already have [flask](https://flask.palletsprojects.com/en/2.1.x/installation/?highlight=installation), [requests_oauthlib ](https://requests-oauthlib.readthedocs.io/en/latest/)and [requests](https://flask.palletsprojects.com/en/2.1.x/installation/?highlight=installation) installed you will need to install these libraries.

You will first need to import the following libraries:

```python
import base64
import hashlib
import os
import re
import requests

from requests_oauthlib import OAuth2Session
from flask import Flask, request, redirect, session, url_for
```

First, you will need to set a variable for your `app`, which is the start of every Flask app. Additionally, you will need to set a secret key for your app to be a random string. 

```python
app = Flask(__name__)
app.secret_key = os.urandom(50)
```

The two main credentials you will need to authenticate with OAuth 2.0 Authorization Code Flow with PKCE are Client ID and Client Secret. You can set these as environment variables to ensure security. 

```python
client_id = os.environ.get("CLIENT_ID")
client_secret = os.environ.get("CLIENT_SECRET")
```

You will also need to set a variable for your redirect URL, this should be the same value as your callback URL in your App’s settings in the Developer Portal.

```python
redirect_uri = os.environ.get("REDIRECT_URI")
auth_url = "https://twitter.com/i/oauth2/authorize"
token_url = "https://api.twitter.com/2/oauth2/token"
```

To define the permissions of your App, you will need to set the scopes of your application. Check out the [authentication mapping guide](https://developer.twitter.com/en/docs/authentication/guides/v2-authentication-mapping) to determine what scopes you will need based on the endpoints you are using. The scope `offline.access` allows you to generate refresh tokens. The scopes for this demo are `tweet.read` and `users.read`, which gives you access to read Tweets and obtain information about users.

```python
scopes = ["tweet.read", "users.read", "offline.access"]
```

You will need to set a code verifier which is a secure random string. The code verifier is used to create the code challenge.

```python
code_verifier = base64.urlsafe_b64encode(os.urandom(30)).decode("utf-8")
code_verifier = re.sub("[^a-zA-Z0-9]+", "", code_verifier
```

With PKCE the code challenge is a base64 encoded string of the SHA256 hash of the code verifier.

```python
code_challenge = hashlib.sha256(code_verifier.encode("utf-8")).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode("utf-8")
code_challenge = code_challenge.replace("=", "")

```

Now you can start with creating a login page that will be the first page you will visit when you run your application. `@app.route("/")` indicates it’s the first landing page. This page will be the page that an authenticated user logs into.

```python
@app.route("/")
def demo():
    global twitter
    twitter = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scopes)
    authorization_url, state = twitter.authorization_url(
        auth_url, code_challenge=code_challenge, code_challenge_method="S256"
    )
    session["oauth_state"] = state
    return redirect(authorization_url)

```

After the user logs in, they will be directed to the callback. In the callback you will generate an object called `token` that contains a series of tokens that includes an access token and a refresh token. 

```python
@app.route("/oauth/callback", methods=["GET"])
def callback():
    code = request.args.get("code")
    token = twitter.fetch_token(
        token_url=token_url,
        client_secret=client_secret,
        code_verifier=code_verifier,
        code=code,
    )
```

Now that you’ve generated a token object, you can now make a request to the authenticated user lookup, to obtain a user ID that can be used to access many of our v2 Users endpoints such as manage Tweets or manage blocks. 
The `access_token` variable inside of the token object is the bearer token you’d use to connect to any of the endpoints that support OAuth 2.0 Authorization Code Flow with PKCE.

```python
    user_me = requests.request(
        "GET",
        "https://api.twitter.com/2/users/me",
        headers={"Authorization": "Bearer {}".format(token["access_token"])},
    ).json()
    print(user_me)
    user_id = user_me["data"]["id"]
```

Now that you’ve created a token and saved into a dictionary you can now access the token dictionary and create a refreshed token through this process. This code should be added to your callback method. This `refreshed_token` will contain a new access token as well.

```python
tokens = {"new_token": token}
    t = tokens["new_token"]
    refreshed_token = twitter.refresh_token(
          client_id=client_id,
          client_secret=client_secret,
          token_url=token_url,
          refresh_token=t["refresh_token"],
        )
    tokens.update({"new_token": refreshed_token})
    return "You should now have a refreshed token"


if __name__ == "__main__":
    app.run()
```

## Full code

Here is the full code that you can save as `app.py`

```python
import base64
import hashlib
import os
import re
import requests

from requests_oauthlib import OAuth2Session
from flask import Flask, request, redirect, session, url_for


app = Flask(__name__)
app.secret_key = os.urandom(50)


client_id = os.environ.get("CLIENT_ID")
client_secret = os.environ.get("CLIENT_SECRET")

redirect_uri = os.environ.get("REDIRECT_URI")
auth_url = "https://twitter.com/i/oauth2/authorize"
token_url = "https://api.twitter.com/2/oauth2/token"

scopes = ["tweet.read", "users.read", "offline.access"]

code_verifier = base64.urlsafe_b64encode(os.urandom(30)).decode("utf-8")
code_verifier = re.sub("[^a-zA-Z0-9]+", "", code_verifier)

code_challenge = hashlib.sha256(code_verifier.encode("utf-8")).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode("utf-8")
code_challenge = code_challenge.replace("=", "")


@app.route("/")
def demo():
    global twitter
    twitter = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scopes)
    authorization_url, state = twitter.authorization_url(
        auth_url, code_challenge=code_challenge, code_challenge_method="S256"
    )
    session["oauth_state"] = state
    return redirect(authorization_url)


@app.route("/oauth/callback", methods=["GET"])
def callback():
    code = request.args.get("code")
    token = twitter.fetch_token(
        token_url=token_url,
        client_secret=client_secret,
        code_verifier=code_verifier,
        code=code,
    )
    user_me = requests.request(
        "GET",
        "https://api.twitter.com/2/users/me",
        headers={"Authorization": "Bearer {}".format(token["access_token"])},
    ).json()
    print(user_me)
    user_id = user_me["data"]["id"]
    tokens = {"new_token": token}
    t = tokens["new_token"]
    refreshed_token = twitter.refresh_token(
          client_id=client_id,
          client_secret=client_secret,
          token_url=token_url,
          refresh_token=t["refresh_token"],
        )
    tokens.update({"new_token": refreshed_token})
    return "You should now have a refreshed token"


if __name__ == "__main__":
    app.run()

```

To run the file locally you can run the following line: 

```
python app.py
```

## Next steps

Hopefully, this can be a starting point for you to get started with generating refresh tokens. As a next step, if you are using Flask you may want to consider using a [schedular](https://github.com/viniciuschiele/flask-apscheduler) to update your refresh tokens regularly in an automated fashion. Additionally you may want to consider saving your tokens to a database in a secure fashion. This code sample can also be extended to allow you to connect to any of the endpoints that support v2 and can be deployed to a server as part of a more complete application.

Be sure to let us know on the [forums](http://twittercommunity.com/) if you run into any troubles along the way, or Tweet us at [@TwitterDev](https://twitter.com/TwitterDev) if this tutorial inspires you to create anything.