Programmatic access
This page describes how to access Pomerium endpoints programmatically.
Configuration
Every identity provider has slightly different methods for issuing OAuth 2.0 access tokens suitable for machine-to-machine use, please review your identity provider's documentation. For example:
- Google Oauth2 2.0 for Desktop Apps
- Okta PKCE Flow
- Azure Active Directory using the OAuth 2.0 code grant flow
For the sake of illustration, this guide and example scripts will use Google as the underlying identity provider.
Identity Provider Configuration
To configure programmatic access for Pomerium we'll need to set up an additional OAuth 2.0 client ID that can issue id_tokens
whose audience matches the Client ID of Pomerium. Follow these instructions adapted from Google's documentation:
- Go to the Credentials page.
- Select the project with the Pomerium secured resource.
- Click Create credentials, then select OAuth Client ID.
- Under Application type, select Other, add a Name, then click Create.
- On the OAuth client window that appears, note the client ID and client secret.
- On the Credentials window, your new Other credentials appear along with the primary client ID that's used to access your application.
High level flow
The application interacting with Pomerium will roughly have to manage the following access flow.
- A user authenticates with the OpenID Connect identity provider. This typically requires handling the Proof Key for Code Exchange process.
- Exchange the code from the Proof Key for Code Exchange for a valid
refresh_token
. - Using the
refresh_token
from the last step, request the identity provider issue a newid_token
which has our Pomerium app'sclient_id
as theaudience
. - Exchange the identity provider issued
id_token
for apomerium
token (e.g.https://authenticate.{your-domain}/api/v1/token
). - Use the pomerium issued
Token
authorization bearer token for all requests to Pomerium protected endpoints until it'sExpiry
. Authorization policy will be tied to the user as normal.
Expiration and revocation
Your application should handle token expiration. If the session expires before work is done, the identity provider issued refresh_token
can be used to create a new valid session by repeating steps 3 and on.
Also, you should write your code to anticipate the possibility that a granted refresh_token
may stop working. For example, a refresh token might stop working if the underlying user changes passwords, revokes access, or if the administrator removes rotates or deletes the OAuth Client ID.
Example Code
It's not as bad as it sounds. Please see the following minimal but complete examples.
Python
python scripts/programmatic_access.py --client-secret REPLACE_ME \
--client-id 851877082059-85tfqg9hlm8j9km5d9uripd0dvk72mvk.apps.googleusercontent.com \
--pomerium-client-id 851877082059-bfgkpj09noog7as3gpc3t7r6n9sjbgs6.apps.googleusercontent.com
from __future__ import absolute_import, division, print_function
import argparse
import json
import sys
import requests
parser = argparse.ArgumentParser()
parser.add_argument('--openid-configuration',
default="https://accounts.google.com/.well-known/openid-configuration")
parser.add_argument('--client-id')
parser.add_argument('--client-secret')
parser.add_argument('--pomerium-client-id')
parser.add_argument('--code')
parser.add_argument('--pomerium-token-url',
default="https://authenticate.corp.beyondperimeter.com/api/v1/token")
parser.add_argument('--pomerium-token')
parser.add_argument('--pomerium-url', default="https://httpbin.corp.beyondperimeter.com/get")
def main():
args = parser.parse_args()
code = args.code
pomerium_token = args.pomerium_token
oidc_document = requests.get(args.openid_configuration).json()
token_url = oidc_document['token_endpoint']
print(token_url)
sign_in_url = oidc_document['authorization_endpoint']
if not code and not pomerium_token:
if not args.client_id:
print("client-id is required")
sys.exit(1)
sign_in_url = "{}?response_type=code&scope=openid%20email&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_id={}".format(
oidc_document['authorization_endpoint'], args.client_id)
print("Access code not set, so we'll do the process interactively!")
print("Go to the url : {}".format(sign_in_url))
code = input("Complete the login and enter your code:")
print(code)
if not pomerium_token:
req = requests.post(
token_url, {
'client_id': args.client_id,
'client_secret': args.client_secret,
'code': code,
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'grant_type': 'authorization_code'
})
refresh_token = req.json()['refresh_token']
print("refresh token: {}".format(refresh_token))
print("create a new id_token with our pomerium app as the audience")
req = requests.post(
token_url, {
'refresh_token': refresh_token,
'client_id': args.client_id,
'client_secret': args.client_secret,
'audience': args.pomerium_client_id,
'grant_type': 'refresh_token'
})
id_token = req.json()['id_token']
print("pomerium id_token: {}".format(id_token))
print("exchange our identity providers id token for a pomerium bearer token")
req = requests.post(args.pomerium_token_url, {'id_token': id_token})
pomerium_token = req.json()['Token']
print("pomerium bearer token is: {}".format(pomerium_token))
req = requests.get(args.pomerium_url, headers={'Authorization': 'Bearer ' + pomerium_token})
json_formatted = json.dumps(req.json(), indent=1)
print(json_formatted)
if __name__ == '__main__':
main()
Bash
#!/bin/bash
# Create a new OAUTH2 provider DISTINCT from your pomerium configuration
# Select type as "OTHER"
CLIENT_ID='REPLACE-ME.apps.googleusercontent.com'
CLIENT_SECRET='REPLACE-ME'
SIGNIN_URL='https://accounts.google.com/o/oauth2/v2/auth?client_id='$CLIENT_ID'&response_type=code&scope=openid%20email&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob'
# This would be your pomerium client id
POMERIUM_CLIENT_ID='REPLACE-ME.apps.googleusercontent.com'
echo "Follow the following URL to get an offline auth code from your IdP"
echo $SIGNIN_URL
read -p 'Enter the authorization code as a result of logging in: ' CODE
echo $CODE
echo "Exchange our authorization code to get a refresh_token"
echo "refresh_tokens can be used to generate indefinite access tokens / id_tokens"
curl \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET \
-d code=$CODE \
-d redirect_uri=urn:ietf:wg:oauth:2.0:oob \
-d grant_type=authorization_code \
https://www.googleapis.com/oauth2/v4/token
read -p 'Enter the refresh token result:' REFRESH_TOKEN
echo $REFRESH_TOKEN
echo "Use our refresh_token to create a new id_token with an audience of pomerium's oauth client"
curl \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET \
-d refresh_token=$REFRESH_TOKEN \
-d grant_type=refresh_token \
-d audience=$POMERIUM_CLIENT_ID \
https://www.googleapis.com/oauth2/v4/token
echo "now we have an id_token with an audience that matches that of our pomerium app"
read -p 'Enter the resulting id_token:' ID_TOKEN
echo $ID_TOKEN
curl -X POST \
-d id_token=$ID_TOKEN \
https://authenticate.corp.beyondperimeter.com/api/v1/token
read -p 'Enter the resulting Token:' POMERIUM_ACCESS_TOKEN
echo $POMERIUM_ACCESS_TOKEN
echo "we have our bearer token that can be used with pomerium now"
curl \
-H "Authorization: Bearer ${POMERIUM_ACCESS_TOKEN}" \
"https://httpbin.corp.beyondperimeter.com/"