OAuth 2.0 authentication is a secure way for users to give an application limited access to their Zendesk data without giving away their password. As an app developer, you can use OAuth to safely access Zendesk data on behalf of your users.

In this tutorial, you'll build a basic web app that uses OAuth to authenticate users with Zendesk. It starts by creating an access token and a refresh token for the user. The app then uses the access token to authenticate a request to the Zendesk API. The API request retrieves the user's Zendesk profile data.

The app includes a fallback mechanism to handle expired or missing tokens. The app uses a refresh token to create a new access token after the previous one has expired.

After making the request, the app displays a web page that contains the user's Zendesk user name and role. You can use the app's code as a starting point for building your own OAuth flows with Zendesk.

Disclaimer: Zendesk provides this article for instructional purposes only. Zendesk doesn't provide support for the app or example code in this tutorial. Zendesk doesn't support third-party technologies such as Python and third-party libraries.

Note: This article covers how to use OAuth in an external application to access Zendesk data. To use OAuth in a Zendesk app to access data from an external system, see Adding third-party OAuth to a Support app.

Authorization code grant type

Zendesk supports the OAuth 2.0 authorization code grant type, also called the authorization code flow. For an overview of the authorization code grant type and its workflow, see Implementing an OAuth authorization code flow in your application in Zendesk help.

Handling expired tokens

Your application should have a mechanism to handle expired access and refresh tokens.

For example, if the access token expires or encounters an error, the app should refresh it. If the refresh process fails or there is no refresh token linked to the access token, the app should redirect the user to https://{subdomain}.zendesk.com/oauth/authorizations/new to re-authorize your application. See Replacing expired access tokens in Zendesk help.

If the user has previously authorized a token with the same scopes for your app, and that token is still valid and has not been removed from the Zendesk system, they won't need to re-authorize the app. If the token has expired and has been revoked, or if the app requests different scopes, the user will be prompted to grant access to your app again.

What you'll need

To complete this tutorial, you'll need the following free resources:

  • A Zendesk account with admin access. If you don't have an account, you can get a 14-day free trial.
  • Python, an easy-to-use programming language
  • Flask, a lightweight Python web application framework
  • requests, a user-friendly Python library for making web requests
  • python-dotenv, a library designed to make working with environment variables simpler
  • loguru, a library designed to simplify logging

Registering your app with Zendesk Support

Before you can set up OAuth for an app, you need to register the app with Zendesk. You do this by creating an OAuth client.

To create an OAuth client, follow the instructions in Registering your application with Zendesk in Zendesk help. You must be signed in as a Zendesk admin to create an OAuth client.

When registering the app:

  • For the Name and Identifier fields, provide any name and identifier you want.
  • For the Redirect URLs field, specify http://localhost:3000/zendesk/oauth/callback. You'll create this URL later in the tutorial.
  • For the Client kind field, select Confidential.

After you create the client, securely save the client's Identifier and Secret values. You'll use these values later to create OAuth tokens.

Setting your environment variables

You can use the python-dotenv library to set environment variables automatically each time the app starts. The library reads the variables from a file named .env in your project.

Gather your client's identifier and secret, as well as your Zendesk subdomain. For example, if your Zendesk address is https://mondocam.zendesk.com, then your subdomain is "mondocam".

To set the environment variables

  1. In your project folder, create a text file named .env (with a leading period).

  2. Use a text editor to paste the environment variables and their values into the file:

    ZENDESK_CLIENT_ID=your_client_identifierZENDESK_CLIENT_SECRET=your_client_secretZENDESK_SUBDOMAIN=your_subdomainREDIRECT_URI=http://localhost:3000/zendesk/oauth/callback

    Note that the values don't use quotes.

  3. Save the updated .env file in your project folder.

  4. If you're using GitHub for this project, add .env to your .gitignore file so it doesn't get pushed to the remote branch where others could access it.

Creating a web app that uses OAuth

Next, create a web app that uses OAuth to connect to Zendesk. The app should handle each step of the OAuth process:

  • Getting the user to grant your app access to their Zendesk account
  • After they grant access, exchanging an authorization code for an access token and a refresh token
  • Making Zendesk API requests on behalf of the user using their access token. Specifically, this app makes a call to the Show Self endpoint to retrieve the user's name and role.
  • Refreshing the tokens when they expire.

Creating the routes for the app

A route in a web app associates a function with a specific URL pattern. When a user visits that URL in a web browser (or makes an HTTP request to it with any other client), the app calls the corresponding function to handle the request and send back a response.

You'll need the following two routes for your application's basic functionality:

  • one route to a page where the user can request their user profile
  • one route to a page that retrieves and shows the user's profile

You'll need an additional three routes for the authorization code flow:

  • one route asking the user to visit a Zendesk page to grant the application access to their Zendesk account
  • one route to request an authorization code from Zendesk
  • one route to exchange the authorization code for an access and a refresh token

To create the routes

  1. In your project folder, create a file named app.py.

  2. Paste the following code into the file.

    import osfrom urllib.parse import urlencode
    from flask import Flask, redirect, request, sessionimport requestsfrom loguru import loggerfrom dotenv import load_dotenvload_dotenv()   # set the environment variables
    import backend_code
    app = Flask(__name__)app.secret_key = os.urandom(24)   # required to sign session cookies
    BASE_URL = f"https://{os.environ['ZENDESK_SUBDOMAIN']}.zendesk.com"
    
    # --- Application routes --- #
    @app.route('/')def index():    logger.info('Opening the home page')    session['user_id'] = '[email protected]'    # provided at sign-in    return f"<p><a href='/profile'>Get my profile</a></p>"
    
    @app.route('/profile')def show_profile():    logger.info('Retrieving the user\'s access and refresh tokens')    tokens = backend_code.retrieve_oauth_tokens_from_db()    if tokens.get('error') == 'tokens_not_found':        logger.info('The user has not granted access to this app yet. '                    'Asking the user')        return redirect('/grant_access')
        logger.info('Making the API request')    url = f"{BASE_URL}/api/v2/users/me"    headers = {'Authorization': f"Bearer {tokens['access_token']}"}    response = requests.get(url, headers=headers)
        if response.status_code == 401:        logger.info('The access token has expired. '                    'Refreshing the user\'s tokens and retrying')        tokens = backend_code.refresh_tokens(tokens['refresh_token'])
            if tokens.get('error') == 'invalid_refresh_token':            logger.info('The refresh token has also expired. '                        'Asking the user to grant access again')            return redirect('/grant_access')
            logger.info('Retrying the API request with the new token')        headers['Authorization'] = f"Bearer {tokens['access_token']}"        response = requests.get(url, headers=headers)
        logger.info('Using the response data')    data = response.json()    user_name = data['user']['name']    user_role = data['user']['role']    return f"""<p>👋 Hi, {user_name}!</p>        <p>Your Zendesk user role is <strong>{user_role}</strong>.</p>        """
    
    # --- Authorization code flow routes --- #
    @app.route("/grant_access")def grant_access():    return (        '<p>The app needs to access your Zendesk account.</p>'        '<p><a href="/zendesk/auth">Grant access</a> to your Zendesk account.</p>'        '<p>You\'ll be redirected to the home page after granting access.</p>'    )
    
    @app.route('/zendesk/auth')def auth():    logger.info('Kicking off the authorization code flow')    parameters = urlencode(        {            'response_type': 'code',            'client_id': os.environ['ZENDESK_CLIENT_ID'],            'redirect_uri': os.environ['REDIRECT_URI'],            'expires_in': 360,              # 6 minutes            'scope': 'users:read',        }    )    return redirect(f"{BASE_URL}/oauth/authorizations/new?{parameters}")
    
    @app.route('/zendesk/oauth/callback')def auth_callback():    logger.info('Exchanging the authorization code for an access and refresh token')    data = {        'grant_type': 'authorization_code',        'client_id': os.environ['ZENDESK_CLIENT_ID'],        'client_secret': os.environ['ZENDESK_CLIENT_SECRET'],        'redirect_uri': os.environ['REDIRECT_URI'],        'code': request.args.get('code'),        'scope': 'users:read'    }    url = f"{BASE_URL}/oauth/tokens"    response = requests.post(url, data=data)    data = response.json()    tokens = {        'access_token': data['access_token'],        'refresh_token': data['refresh_token']    }    backend_code.store_oauth_tokens_in_db(tokens)    return redirect('/')
    
    if __name__ == "__main__":    app.run(debug=True, host="0.0.0.0", port=3000)

Here's a summary of what the code does:

  • When a user requests their profile, the app first checks whether a valid OAuth access token is stored for that user.

  • If the token is missing or expired, the app redirects the user to Zendesk’s authorization page to grant access. After the user authorizes the app, Zendesk redirects back with an authorization code, which the app exchanges for access and refresh tokens. The app stores the new tokens in the database for future API requests.

  • Finally, the app uses the stored access token to call the Zendesk API and fetch the authenticated user’s profile information.

For simplicity, this code imports and uses a module called backend_code that contains helper functions to store, retrieve, and refresh the tokens. The next section explains how to create this backend code.

Create a simulated backend for the app

Web applications typically consist of routes for controlling the frontend and additional services in the backend that don't require user input. The simulated backend for the tutorial application provides the app with three services:

  • Takes a previous refresh token and exchanges it for new access and refresh tokens
  • Retrieves OAuth tokens stored in a database
  • Stores OAuth tokens in a database

For simplicity, the database is simulated with a JSON file named users.json. Don't create the users.json file yourself. The app will create it when it runs for the first time.

Refreshing the tokens can be handled in the backend because the process doesn't involve any user interaction.

To create the backend code

  1. In your project folder, create a file called backend_code.py.

  2. Paste the following code into the file.

    import osimport json
    import requestsfrom flask import sessionfrom loguru import logger
    BASE_URL = f"https://{os.environ['ZENDESK_SUBDOMAIN']}.zendesk.com"USERS_DB = 'users.json'
    
    def refresh_tokens(refresh_token) -> dict:    """    Generates a new pair of access and refresh tokens using a refresh    token while invalidating the previous ones. If the refresh token is    invalid or expired, it returns an error. Otherwise, it updates the    stored tokens in the database and returns the new tokens.    """    logger.info('Using the refresh token to get new access and refresh tokens')    response = requests.post(        f"{BASE_URL}/oauth/tokens",        data={            'grant_type': 'refresh_token',            'refresh_token': refresh_token,            'client_id': os.environ['ZENDESK_CLIENT_ID'],            'client_secret': os.environ['ZENDESK_CLIENT_SECRET'],            'expires_in': 360,            'refresh_token_expires_in': 604800,            'scope': 'users:read'        }    )
        if response.status_code == 400 and 'invalid_grant' in response.text:        logger.info('The refresh token is invalid. Returning an error message')        return {'error': 'invalid_refresh_token'}
        logger.info('Packaging, storing, and returning the new tokens')    data = response.json()    tokens = {        'access_token': data['access_token'],        'refresh_token': data['refresh_token']    }    store_oauth_tokens_in_db(tokens)    return tokens
    
    def retrieve_oauth_tokens_from_db() -> dict:    """    Retrieves the signed-in user’s tokens from the database using the    user id stored in the session. If no tokens are found, it returns    an error indicating that the user needs to authorize the app.    """    if os.path.exists(USERS_DB):        with open(USERS_DB, 'r') as f:            user_records = json.load(f)        user_id = session.get('user_id')        if user_id in user_records:            return user_records[user_id]
        return {'error': 'tokens_not_found'}
    
    def store_oauth_tokens_in_db(tokens) -> None:    """    Stores the user’s access and refresh tokens to the database,    associating them with the user’s user_id from the session.    """    logger.info('Storing the access and refresh tokens in the database')    user_id = session.get('user_id')    # print(f"in store tokens: {user_id}")    user_record = {user_id: tokens}    with open(USERS_DB, 'w') as f:        json.dump(user_record, f, sort_keys=True, indent=2)

Starting the app

To start the app

  1. In your terminal, navigate to your project folder.

  2. Run the following command:

    python3 app.py

    If you're running it inside a virtual environment, use python app.py.

  3. Paste the URL that appears in the startup log messages into your browser's address bar.

    The URL should be http://127.0.0.1:3000.

  4. To stop the app, press Ctrl+C.

Testing the app's OAuth flow

The last step is to test your app to ensure the authentication flow works as intended.

Make sure your terminal is visible as you test the app. You can monitor the log messages displayed in the terminal to see what's happening in the app in real time.

To test the app

  1. Start the app as described in Starting the app.

  2. In a web browser, go to http://localhost:3000 and click Get my profile.

    The first time you use the app, it should kick off the authorization code flow to get your initial tokens.

  3. If prompted, sign in to Zendesk.

    A Zendesk OAuth page should open in a new tab after signing in.

  4. Click Allow.

    The page closes and redirects back to your app's home page.

    Click Get my profile to retry the request.

    The app displays a web page that contains your user name and role.

    If you wanted, you could repeat this process using multiple browsers and Zendesk users.

  5. Change a digit in the access token in the users.json database file to simulate an expired token, then rerun the app.

    Follow the log messages in your terminal to see if the app successfully creates a new set of tokens with the refresh token, and then uses the new access token to get the user profile.

  6. Change a digit in both the access token and the refresh token to simulate an expired refresh token, then rerun the app.

    Follow the log messages in your terminal to see if the app kicks off the authorization code flow to ask the user to grant it access to their account again.

Congratulations! You've created a web app that connects with Zendesk using OAuth. As a next step, you could expand the app and get it ready for production.