Skip to content

Authentication

There are three main ways to authenticate with Anaplan.

  • Basic Authentication
  • Certificate Authentication
  • OAuth2

Anaplan SDK supports all of them, though Basic Authentication is strictly not recommended for production use. Certificate Authentication is currently the most suitable for production use, since the Anaplan OAuth 2.0 implementation does not support the client_credentials grant type. This means you will have to manually manage the refresh Token if you choose to use OAuth2.

Basic Authentication

Basic Authentication is the simplest way to authenticate with Anaplan. It is unsuitable for Production. Anaplan password policies force password changes every 30, 60 or 90 days, depending on tenant settings, making this approach annoying to maintain and error-prone.

import anaplan_sdk

anaplan = anaplan_sdk.Client(
    workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    model_id="11111111111111111111111111111111",
    user_email="admin@company.com",
    password="my_super_secret_password",
)
import anaplan_sdk

anaplan = anaplan_sdk.AsyncClient(
    workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    model_id="11111111111111111111111111111111",
    user_email="admin@company.com",
    password="my_super_secret_password",
)

Certificate Authentication

Certificate Authentication is the most suitable for production use. It uses an X.509 S/MIME Certificate (aka. Client Certificate or HTTPS-Certificate) and Private Key. The Process of acquiring such a certificate is well documented. Anaplan does not support self-signed certificates, so you will need to procure a certificate from a trusted Certificate Authority (CA).

Requires Extra

If you want to use certificate authentication, you need to install the cert extra:

pip install anaplan-sdk[cert]
uv add anaplan-sdk[cert]
poetry add anaplan-sdk[cert]

This will install cryptography to securely construct the authentication request.

import anaplan_sdk

anaplan = anaplan_sdk.Client(
    workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    model_id="11111111111111111111111111111111",
    certificate="~/certs/anaplan.pem",
    private_key="~/keys/anaplan.pem",
    private_key_password="my_super_secret_password", # Optional
)
import anaplan_sdk

anaplan = anaplan_sdk.AsyncClient(
    workspace_id="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    model_id="11111111111111111111111111111111",
    certificate="~/certs/anaplan.pem",
    private_key="~/keys/anaplan.pem",
    private_key_password="my_super_secret_password", # Optional
)

OAuth for Web Applications

If you are building a Web Application and intend to have your users authenticate with Anaplan, you can use the Oauth or AsyncOauth classes. These classes provide the necessary utilities to handle the Oauth2 authorization_code grant, in which the authentication flow must occur outside the SDK for the user to log in.

These Classes exist for convenience only, and you can use any other Library to handle the Oauth2 flow.

A minimal, illustrative example for FastAPI is shown below, but you can use any other Web Framework. This will not run until you implement the TODOs in a suitable way for your purpose.

Requires Extra

If you want to use OAuth2 authentication, you need to install the oauth extra:

pip install anaplan-sdk[oauth]
uv add anaplan-sdk[oauth]
poetry add anaplan-sdk[oauth]

This will install OAuthLib to securely construct the authentication request.

import os
from typing import Annotated

from fastapi import FastAPI, HTTPException, Request, Security
from fastapi.responses import RedirectResponse

from anaplan_sdk import AsyncClient, AsyncOauth, exceptions

_oauth = AsyncOauth(
    client_id=os.environ["OAUTH_CLIENT_ID"],
    client_secret=os.environ["OAUTH_CLIENT_SECRET"],
    redirect_uri="https://vinzenzklass.github.io/anaplan-sdk/oauth/callback",
)

app = FastAPI()


@app.get("/login")
async def login():
    # TODO: Store the state for subsequent validation.
    url, state = _oauth.authorization_url()
    return RedirectResponse(url, status_code=302)


@app.get("/oauth/callback")
async def oauth_callback(req: Request):
    # TODO: Validate the state and handle the token.
    token = await _oauth.fetch_token(str(req.url))
    return RedirectResponse("/home", status_code=303)


async def _validate_session(
        token: Annotated[dict[str, str], Security(...)],
) -> AsyncClient:
    # TODO: Implement the Security scheme.
    # TODO: Refresh the token if it is expired.
    token = await _oauth.refresh_token(token["refresh_token"])
    await _oauth.validate_token(token["access_token"])
    return AsyncClient(token=token["access_token"])


@app.get("/profile")
async def profile(anaplan: Annotated[AsyncClient, Security(_validate_session)]):
    return await anaplan.audit.get_user("me")


@app.exception_handler(exceptions.InvalidCredentialsException)
async def invalid_credentials_exception_handler(_, __):
    raise HTTPException(
        status_code=401, detail="Invalid or expired Credentials."
    )

Note that we're only passing the access_token to the AsyncClient. This is the recommended way to instantiate short-lived instances such as in this example, since it has virtually no overhead. It will however not handle token expiration or refresh, so you will need to handle that yourself. If you expect long-lived instances, you can pass an instance of AnaplanRefreshTokenAuth. This will use the existing token to authenticate and will refresh the token when it expires.

anaplan = Client(
    auth=AnaplanRefreshTokenAuth(
        token=token,
        client_id=os.environ["OAUTH_CLIENT_ID"],
        client_secret=os.environ["OAUTH_CLIENT_SECRET"],
        redirect_uri="https://vinzenzklass.github.io/anaplan-sdk",
    )
)
anaplan = AsyncClient(
    auth=AnaplanRefreshTokenAuth(
        token=token,
        client_id=os.environ["OAUTH_CLIENT_ID"],
        client_secret=os.environ["OAUTH_CLIENT_SECRET"],
        redirect_uri="https://vinzenzklass.github.io/anaplan-sdk",
    )
)

OAuth for Local Applications

For local applications, you can use AnaplanLocalOAuth Class to handle the initial Oauth2 authorization_code flow and the subsequent token refreshes.

Requires Extra

If you want to use OAuth2 authentication, you need to install the oauth extra:

pip install anaplan-sdk[oauth]
uv add anaplan-sdk[oauth]
poetry add anaplan-sdk[oauth]

This will install OAuthLib to securely construct the authentication request.

anaplan = Client(
    auth=AnaplanLocalOAuth(
        client_id=os.environ["OAUTH_CLIENT_ID"],
        client_secret=os.environ["OAUTH_CLIENT_SECRET"],
        redirect_uri="https://vinzenzklass.github.io/anaplan-sdk",
    )
)
anaplan = AsyncClient(
    auth=AnaplanLocalOAuth(
        client_id=os.environ["OAUTH_CLIENT_ID"],
        client_secret=os.environ["OAUTH_CLIENT_SECRET"],
        redirect_uri="https://vinzenzklass.github.io/anaplan-sdk",
    )
)

The SDK will prompt you to open the login URI in your browser. After you have logged in, you will need to copy the entire redirect URI from your browser and paste it into the terminal.

Why do I need to copy the redirect URL?

Unfortunately, registering localhost redirect URIs is not supported by Anaplan. This means we cannot intercept the redirect URI and extract the authorization_code automatically. This is a limitation of Anaplan's OAuth2 implementation. See this Community Note.

Persisting OAuth Tokens

The SDK provides the ability to persist OAuth refresh tokens between sessions using the system's secure keyring for local applications. This allows you to avoid having to re-authenticate every time you run your application while using OAuth2.

Requires Extras

If you want to use persisting Tokens, you need to additionally install the keyring extra:

pip install anaplan-sdk[oauth,keyring]
uv add anaplan-sdk[oauth,keyring]
poetry add anaplan-sdk[oauth,keyring]

This will install Keyring to securely store refresh tokens.

To enable token persistence, set the persist_token=True parameter when creating an AnaplanLocalOAuth instance:

anaplan = Client(
    auth=AnaplanLocalOAuth(
        client_id=os.environ["OAUTH_CLIENT_ID"],
        client_secret=os.environ["OAUTH_CLIENT_SECRET"],
        redirect_uri="https://vinzenzklass.github.io/anaplan-sdk",
        persist_token=True,
    )
)
anaplan = AsyncClient(
    auth=AnaplanLocalOAuth(
        client_id=os.environ["OAUTH_CLIENT_ID"],
        client_secret=os.environ["OAUTH_CLIENT_SECRET"],
        redirect_uri="https://vinzenzklass.github.io/anaplan-sdk",
        persist_token=True,
    )
)

When persist_token is set to True, the SDK will:

  • Look for a stored refresh token in the system's keyring
  • If found, use it to obtain a new access token. If also given, this will overwrite the passed token parameter.
  • If not found or if the token is invalid, prompt the user for authentication
  • After authentication, store the new refresh token in the keyring
Keyring Configuration

The keyring library may require additional configuration depending on your environment:

  • In headless environments, you may need to explicitely configure a different keyring backend
  • Some Linux distributions may require additional packages or configuration

Configuring the keyring backend is your responsibility as it depends on your specific environment.

For example, to use the libsecret file backend:

import keyring
from keyring.backends import libsecret

keyring.set_keyring(libsecret.Keyring())

For more information, refer to the keyring documentation.

OAuth Token Ownership

Instances of both AnaplanLocalOAuth and AnaplanRefreshTokenAuth assert ownership of the token you pass to them for their entire lifetime. This means that you should not use the token outside of these classes, as it may lead to errors when attempting to use the same refresh token in multiple places. You can access the current token by using the token property, but you should not use anything other than the access_token. You can use this property to reassert control of the OAuth token when the instance is nor longer needed. If you do need to use the token in several places simultaneously, you should use a custom scheme to do so and handle all potential conflicts appropriately.

Custom Authentication Schemes

If you need more control over the authentication process, you can provide your own Subclass of httpx.Auth to the auth parameter of the Client or AsyncClient. This allows you to implement any custom authentication strategy you need. If you do so, the entire Authentication process is your responsibility. You can read more about the httpx.Auth interface in the httpx documentation.

Below is an outline of the simplest variant of the httpx.Auth interface that will suffice for Anaplan's authentication. Note the non-standard AnaplanAuthToken prefix in the Authorization header and the requires_response_body = True class attribute.

import httpx

class MyCustomAuth(httpx.Auth):
    requires_response_body = True

    def __init__(self, token: str):
        self._token: str = token

    def auth_flow(self, request):
        request.headers["Authorization"] = f"AnaplanAuthToken {self._token}"
        response = yield request
        if response.status_code == 401:
            auth_res = yield httpx.Request(...) # Your implementation
            self._token = auth_res.json()["tokenInfo"]["tokenValue"]
            request.headers["Authorization"] = f"AnaplanAuthToken {self._token}"
            yield request
If you believe that your custom authentication scheme may be generally useful, please consider contributing it to the SDK or opening an issue to discuss it.