In this tutorial, you'll build a web app that implements an OAuth authorization flow. The main goal of OAuth authorization is to allow third-party applications to interact with a Zendesk Support instance without having to store and use the passwords of Zendesk Support users, which is sensitive information that the apps shouldn't know.

When you use basic authentication, you have to specify a username with a password or an API token. Example:

curl https://{subdomain}.zendesk.com/api/v2/tickets.json \  -u {email_address}:{password}

With OAuth authentication, the app specifies an OAuth access token in an HTTP header as follows:

curl https://{subdomain}.zendesk.com/api/v2/tickets.json \  -H "Authorization: Bearer {access_token}"

If the user doesn't have an access token, the app has to send the user to a Zendesk Support authorization page where the user may or may not authorize the app to access Zendesk Support on their behalf. If the user authorizes access, Zendesk Support provides the app with an authorization code that it can exchange for an access token. A different access token is provided for each user who authorizes the app. The app never learns the user's Zendesk Support password.

The goal of this tutorial is to show you how to implement the OAuth authorization grant flow in a web application. To keep things simple, the app is built using a Python micro web framework called Bottle. The framework helps keep technical details to a minimum so you can focus on the authorization flow.

To implement OAuth authentication in Zendesk apps, see Adding third-party OAuth to a Support app.

For more information on using OAuth in Zendesk Support, see Using OAuth authentication with your application.

Disclaimer: Zendesk provides this article for instructional purposes only. Zendesk does not support or guarantee the code. Zendesk also can't provide support for third-party technologies such as Python and the Bottle framework.

What you need

You need a text editor and a command-line interface like the command prompt in Windows or the Terminal on the Mac.

You'll also need Python and a few Python libraries to develop the web app, as described next.

Install Python 3

Install version 3 of Python on your computer if you don't already have it. Python is a powerful but beginner-friendly scripting and programming language with a clear and readable syntax. Visit the Python website to learn more. To download and install it, see http://www.python.org/download/.

If you have any problems, see the pip instructions.

When copying the Python code in this tutorial, make sure to indent lines exactly as shown. Indentation matters in Python.

Install Requests

Requests is a library that simplifies making HTTP requests. Use the following pip command in your command-line interface to download and install:

$ pip3 install requests

If you have any problems, see the Requests instructions.

Install Bottle

Use the following pip command to download and install Bottle, a micro web framework for Python.

$ pip3 install bottle

If you have any problems, see the Bottle instructions.

Get the tutorial app files

You need a working web app before you can implement the OAuth authorization flow. To keep things simple, the tutorial app uses a micro web framework called Bottle. All the logic of a Bottle app can fit comfortably in a single file. Because this isn't a Bottle tutorial, a set of starter files is provided.

To also keep the spotlight on the OAuth flow, the app will limit itself to getting and displaying the user's Zendesk Support user name and role, which could be admin, agent, or end-user.

Topics covered in this section:

Download the tutorial app files

  1. If you don't already have one, create a tutorials folder.

  2. Download oauth_tutorial_app.zip and unzip the file into your tutorials folder.

The zip file contains starter files for the tutorial application. See the next section to learn more about the files and how Bottle works.

Bottle app basics

Navigate to the oauth_tutorial_app folder in a file browser. The app consists of the following folders and files:

/oauth_tutorial    oauth_app.py    /static    /views

The oauth_app.py file is the app's nerve center. Open it in a text editor to take a look:

from bottle import route, template, redirect, static_file, error, request, response, run

@route('/home')def show_home():    return template('home')

@route('/')def handle_root_url():    redirect('/home')

@route('/zendesk_profile')def make_request():    # make API request    profile_data = {'name': 'Lea Lee', 'role': 'admin'}    return template('details', data=profile_data)

@route('/css/<filename>')def send_css(filename):    return static_file(filename, root='static/css')

@error(404)def error404(error):    return template('error', error_msg='404 error. Nothing to see here')

run(host='localhost', port=8080, debug=True)

The file consist of routes that map HTTP requests to functions. The return value of each function is sent in the HTTP response.

For example, when a browser requests the page at the /home relative URL, the app looks up the /home route and runs the show_home() custom function. The show_home() function returns the results of the framework's template() function in the HTTP response. The template() function renders a template named 'home' in HTML. Templates are located in the views folder and have .tpl file extensions.

Here's the home.tpl template:

<html>  <head>    <title>Oauth Tutorial App Home</title>    <link href="/css/main.css" rel="stylesheet">  </head>  <body>     <h2>Welcome to the OAuth tutorial app</h2>     <a href="zendesk_profile">Get Zendesk profile info about myself.</a>  </body></html>

The home template includes a link to "zendesk_profile", which is handled by the /zendesk_profile route in the oauth_app.py file:

@route('/zendesk_profile')def make_request():    # Get user data    profile_data = {'name': 'Lea Lee', 'role': 'admin'}    return template('details', data=profile_data)

The template() function passes the information contained in the profile_data variable to the template using the data keyword argument. The argument name is arbitrary. For now, the function sends dummy data until you add the request code later.

The argument values are inserted at placeholders in the details.tpl template:

<p>Hello, {{data['name']}}. You're a Zendesk Support {{data['role']}}.</p>

The placeholders contain standard Python expressions for reading data in a dictionary. Possible values for data['role'] are 'end-user', 'agent', and 'admin'.

Finally, the oauth_app.py file calls the framework's run() function to run the app on a local web server:

run(host='localhost', port=8080, debug=True)

The Bottle framework includes a built-in development server that you can use to test your changes locally before deploying them to a remote server.

To learn more about the framework, see Bottle: Python Web Framework on the Bottle website.

Run the app

  1. In your command-line interface, navigate to the oauth_tutorial_app folder.

  2. Run the following command to start the local server:

    $ python3 oauth_app.py
  3. In a browser, go to http://localhost:8080/home.

    You should see the tutorial app's admittedly plain home page:

  4. Run some tests.

    Try clicking the link on the home page. Try opening http://localhost:8080/.

  5. When you're done, switch to your command-line interface and press Ctrl+C to shut down the server.

You're ready to start implementing the OAuth authorization flow. The first step is to register your new app with Zendesk Support.

Register your app with Zendesk Support

Before you can use OAuth authentication, you need to tell Zendesk Support about your application. You must be signed in as a Zendesk Support administrator to register an app.

  1. In Admin Center, click the Apps and integrations icon () in the sidebar, then select APIs > Zendesk API.

  2. Click the OAuth Clients tab on the Zendesk API page, and then click Add a Client on the right side of the client list.

    A page for registering your application appears. The Secret field is pre-populated.

  3. Complete the following fields:

    • Client Name - Enter OAuth Tutorial App. This is the name that users will see when asked to grant access to your application and when they check the list of third-party apps that have access to their Zendesk Support instance.

    • Description - Optional, but you can enter something like "This app gets information from your Zendesk Support profile." Users will see this short description when asked to grant access to it.

    • Company - Optional, but you can enter your company's name if you like. This is the company name that users will see when asked to grant access to your application. The information can help them understand who they're granting access to.

    • Logo - Optional, but you can upload the logo-small.jpg sample image in the /static/images/ folder in the starter files. This is the logo that users will see when asked to grant access to your application. The image can be a JPG, GIF, or PNG. For best results, upload a square image. It'll be resized for the authorization page.

    • Unique Identifier - Click the field to auto-populate it with the name you entered for your app. You can change it if you want.

    • Redirect URLs - Enter the following URL:

      http://localhost:8080/handle_user_decision

      This is the URL that Zendesk Support will use to send the user's decision to grant access to your application. You'll create a route for handle_user_decision later.

  4. Click Save.

    You'll be prompted to save the secret on the next page.

  5. Copy the Secret value and save it somewhere safe.

    The characters may extend past the width of the text box, so make sure to select everything before copying.

    Important: For security reasons, your secret is displayed fully only once. After clicking Save, you'll only have access to the first nine characters.

  6. Click Save.

Now that you've registered the app with Zendesk Support, you can modify it to send users to a Zendesk Support authorization page the first time they attempt to use your app to access Zendesk Support data.

Send the user to the Zendesk Support authorization page

The first change to make to the app is to send users to the Zendesk Support authorization page if they haven't authorized your app yet. Here's the logic that you'll implement: If the user has an access token, make the request. If not, kick off the authorization flow.

  1. Open the oauth_app.py file and change the /zendesk_profile route as follows:

    @route('/zendesk_profile')def make_request():    has_token = False    if has_token:        # Get user data        profile_data = {'name': 'Lea Lee', 'role': 'admin'}        return template('details', data=profile_data)

    Make sure the lines are indented as shown.

    The make_request() function starts by checking to see if the user has an access token. If they have a token (if has_token is true), the app makes the API request right away. You'll replace the temporary has_token code later in the tutorial. You'll also add the API request code. Dummy data is used for now.

  2. Add an else clause to kick off the authorization flow if the user doesn't have a token (if has_token is false):

    else:    # Kick off authorization flow    parameters = {}    url = 'https://{subdomain}.zendesk.com/oauth/authorizations/new?' + parameters    redirect(url)

    Make sure to replace {subdomain} in the URL with your Zendesk Support subdomain.

    If the user doesn't have a token, they're redirected to the Zendesk Support authorization page.

    You must send certain parameters in a query string with the URL, as described next. Zendesk Support uses the parameters to customize the authorization page for the user.

  3. Add the following name/value pairs to the parameters variable in the else clause:

    parameters = {    'response_type': 'code',    'redirect_uri': 'http://localhost:8080/handle_user_decision',    'client_id': 'oauth_tutorial_app',    'scope': 'read write'}

    Because the app uses the authorization code grant flow, you have to specify 'code' as the response type. The 'redirect_uri' value is where you want Zendesk Support to send the user's decision. The 'client_id' value is the "unique identifier" created when you registered the app in Zendesk Support. The 'scope' value is what the app is asking permission to do in Zendesk Support. If the app will only get data and not create it, you could specify 'read' as the scope.

    The parameters must be url-encoded to be sent in a query string, as described next.

  4. Pass the parameters variable to the urlencode() method in the url variable declaration:

    url = '.../oauth/authorizations/new?' + urlencode(parameters)
  5. Add the following line at the top of the oauth_app.py file to import the urlencode method:

    from urllib.parse import urlencode

    The urllib.parse module is included with Python so you don't have to install it.

The updated route should look as follows with your value for the {subdomain} placeholder:

@route('/zendesk_profile')def make_request():    has_token = False    if has_token:        # Get user data        profile_data = {'name': 'Lea Lee', 'role': 'admin'}        return template('details', data=profile_data)    else:        # kick off authorization flow        parameters = {            'response_type': 'code',            'redirect_uri': 'http://localhost:8080/handle_user_decision',            'client_id': 'oauth_tutorial_app',            'scope': 'read write'}        url = 'https://{subdomain}.zendesk.com/oauth/authorizations/new?' + urlencode(parameters)        redirect(url)

Make sure your code is indented as shown, ignore any line wraps caused by the right margin, and then test that it works:

  1. Start the development server from the command line:

    $ python3 oauth_app.py

    If the server is still running from the previous session, shut it down with Ctrl+C and start it up again for the changes to take effect.

  2. Open http://localhost:8080/home in a browser.

  3. Click Get Zendesk profile info about myself.

    You should be redirected to the Zendesk Support authorization page because has_token is false. If you're not currently signed in to Zendesk Support, you'll be asked to sign in first. This is how Zendesk Support knows who you are.

    Eventually, the authorization page opens:

    Don't click Allow yet. The redirect page doesn't exist yet. You'll work on it next. If you do click Allow, it's not the end of the world. You'll just get a 404 message, as expected.

Note: If you make changes to the oauth_app.py file while the local server is running, you have to stop and restart the server to see the changes. You don't have to restart the server if you make changes to static files like the templates or css. Just refresh the page in the browser.

Handle the user's authorization decision

After the user makes the decision on the Zendesk Support authorization page to allow or deny access to your app, Zendesk Support sends the decision and a few other bits of information to the redirect URL you specified.

If the user decided to authorize the application, Zendesk Support adds a query string that contains an authorization code. Example:

{redirect_url}?code=7xqwtlf3rrdj8uyeb1yf

If the user decided not to authorize the application, Zendesk Support adds a query string that contains error and error_description parameters that inform the app that the user denied access. Example:

{redirect_url}?error=access_denied&error_description=The+end-user+or+authorization+server+denied+the+request

Use the possible query string values to control the flow of your application. Here's the approach you'll take: If the query string contains the string 'error', show an error message. If not, get the access token.

  1. Start by creating a route for the "/handle_user_decision" redirect URL:

    @route('/handle_user_decision')def handle_decision():    if 'error' in request.query_string:        return template('error', error_msg=request.query.error_description)    else:        # Get access token

    The handle_decision() function checks to see if the string 'error' appears in the query string. In the Bottle framework, the request object refers to the current HTTP request and the query_string property refers to the request's query string, if any.

    If 'error' is found, the app renders the error template with the error message from the error_description parameter.

    If 'error' is not found, then an authorization code must have been sent. The app can grab the code from the query string and exchange it for an access token with a POST request to a specific API endpoint, as described next.

  2. In the else clause, define the parameters that must be included in the POST request:

    parameters = {    'grant_type': 'authorization_code',    'code': request.query.code,    'client_id': 'oauth_tutorial_app',    'client_secret': '{your_secret}',    'redirect_uri': 'http://localhost:8080/handle_user_decision',    'scope': 'read'}

    The 'grant_type' value is 'authorization_code' because you're implementing an authorization code grant flow. The 'code' value is the actual authorization code, which is retrieved from the query string with the query.code property of the framework's request object. The 'secret' value is the "Secret" generated when you registered the app in Zendesk Support. The 'redirect_uri' value is the same redirect URL as before.

  3. Add the following statements after the parameters to make the POST request:

    payload = json.dumps(parameters)header = {'Content-Type': 'application/json'}url = 'https://{subdomain}.zendesk.com/oauth/tokens'r = requests.post(url, data=payload, headers=header)

    Make sure to replace {subdomain} in the URL with your Zendesk Support subdomain.

    The requests.post() method from the requests library makes the request. The response from Zendesk Support is assigned to the r variable. (The response identifier is reserved for the Bottle response object used in the next step.)

  4. Handle the response:

    if r.status_code != 200:    error_msg = 'Failed to get access token with error {}'.format(r.status_code)    return template('error', error_msg=error_msg)else:    data = r.json()    response.set_cookie('owat', data['access_token'])    redirect('/zendesk_profile')

    If the request was successful (the HTTP status code was 200), the else clause is executed. The app decodes the json data and gets the token. It then saves the token in a cookie named 'owat' (for oauth web app tutorial) on the user's computer with the framework's response.set_cookie() method.

    Important: To keep things simple, the app saves the token unencrypted in a cookie on the user's machine, but be aware of the security implications. It's like saving a password in a cookie. Somebody could use it to access the user's information on Zendesk Support. Unfortunately, storing access tokens securely is beyond the scope of this tutorial.

    Finally, the user is redirected to the zendesk_profile page, which kicked off the authorization flow.

  5. Add the following libraries used in the code at the top of the file:

    import jsonimport requests

The completed /handle_user_decision route should look as follows (make sure your indentation is correct, ignoring the line wraps caused by the right margin):

@route('/handle_user_decision')def handle_decision():    if 'error' in request.query_string:        return template('error', error_msg=request.query.error_description)    else:        # Get access token        parameters = {            'grant_type': 'authorization_code',            'code': request.query.code,            'client_id': 'oauth_tutorial_app',            'client_secret': '{your_secret}',            'redirect_uri': 'http://localhost:8080/handle_user_decision',            'scope': 'read'}        payload = json.dumps(parameters)        header = {'Content-Type': 'application/json'}        url = 'https://{subdomain}.zendesk.com/oauth/tokens'        r = requests.post(url, data=payload, headers=header)        if r.status_code != 200:            error_msg = 'Failed to get access token with error {}'.format(r.status_code)            return template('error', error_msg=error_msg)        else:            data = r.json()            token = data['access_token']            response.set_cookie('owat', token)            redirect('/zendesk_profile')

Use the access token

Your app can now check to see if the user has an access token. If the token exists, the app can use it to request the user's information from Zendesk Support.

  1. In the /zendesk_profile route, replace the following 2 lines:

    has_token = Falseif has_token:

    with the following line at the same indent level:

    if request.get_cookie('owat'):

    The app checks to see if the cookie named 'owat' exists on the user's computer using the Bottle framework's request.get_cookie() method. If the cookie exists, it gets the user data from Zendesk Support. If the cookie doesn't exist, the app kicks off the authorization flow.

  2. Replace the contents of the if clause with the following the request code:

    # Get user dataaccess_token = request.get_cookie('owat')bearer_token = 'Bearer ' + access_tokenheader = {'Authorization': bearer_token}url = 'https://{subdomain}.zendesk.com/api/v2/users/me.json'r = requests.get(url, headers=header)if r.status_code != 200:    error_msg = 'Failed to get data with error {}'.format(r.status_code)    return template('error', error_msg=error_msg)else:    data = r.json()    profile_data = {        'name': data['user']['name'],        'role': data['user']['role']}    return template('details', data=profile_data)

    Make sure to replace {subdomain} in the URL with your Zendesk Support subdomain.

    The first three lines build the authorization header:

    access_token = request.get_cookie('owat')bearer_token = 'Bearer ' + access_tokenheaders = {'Authorization': bearer_token}

    The rest of the block makes the request with the Authorization header, updates the profile_data variable with the response data, and displays the detail page with the user's profile information. To learn more about making API requests, see Making requests to the Ticketing API.

Code complete

The app is done and ready for testing. Here's the finished version of the oauth_app.py file:

from urllib.parse import urlencodeimport json
import requestsfrom bottle import route, template, redirect, static_file, error, request, response, run

@route('/home')def show_home():    return template('home')

@route('/zendesk_profile')def make_request():    if request.get_cookie('owat'):        # Get user data        access_token = request.get_cookie('owat')        bearer_token = 'Bearer ' + access_token        header = {'Authorization': bearer_token}        url = 'https://your_subdomain.zendesk.com/api/v2/users/me.json'        r = requests.get(url, headers=header)        if r.status_code != 200:            error_msg = 'Failed to get data with error {}'.format(r.status_code)            return template('error', error_msg=error_msg)        else:            data = r.json()            profile_data = {                'name': data['user']['name'],                'role': data['user']['role']}            return template('details', data=profile_data)    else:        # Kick off authorization flow        parameters = {            'response_type': 'code',            'redirect_uri': 'http://localhost:8080/handle_user_decision',            'client_id': 'oauth_tutorial_app',            'scope': 'read write'}        url = 'https://your_subdomain.zendesk.com/oauth/authorizations/new?' + urlencode(parameters)        redirect(url)

@route('/handle_user_decision')def handle_decision():    if 'error' in request.query_string:        return template('error', error_msg=request.query.error_description)    else:        # Get access token        parameters = {            'grant_type': 'authorization_code',            'code': request.query.code,            'client_id': 'oauth_tutorial_app',            'client_secret': 'your_secret',            'redirect_uri': 'http://localhost:8080/handle_user_decision',            'scope': 'read'}        payload = json.dumps(parameters)        header = {'Content-Type': 'application/json'}        url = 'https://your_subdomain.zendesk.com/oauth/tokens'        r = requests.post(url, data=payload, headers=header)        if r.status_code != 200:            error_msg = 'Failed to get access token with error {}'.format(r.status_code)            return template('error', error_msg=error_msg)        else:            data = r.json()            response.set_cookie('owat', data['access_token'])            redirect('/zendesk_profile')

@route('/')def handle_root_url():    redirect('/home')

@route('/css/<filename>')def send_css(filename):    return static_file(filename, root='static/css')

@error(404)def error404(error):    return template('error', error_msg='404 error. Nothing to see here')

run(host='localhost', port=8080, debug=True)

Make sure your indentation is correct, then run the app to test it.

  1. Start the local server:

    $ python3 oauth_app.py
  2. In a browser, go to http://localhost:8080/home.

  3. Run some tests.

    If you can, try using different browsers as different Zendesk Support users.

  4. When you're done, switch to your command-line interface and press Ctrl+C to shut down the server.

You can keep tweaking or adding to the app if you want. See the following resources for more information: