# 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.

## 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.

=== "Synchronous"
    ```python
    import anaplan_sdk

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

=== "Asynchronous"
    ```python
    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](https://help.anaplan.com/procure-ca-certificates-47842267-2cb3-4e38-90bf-13b1632bcd44). Anaplan does not support self-signed certificates, so you will need to procure a certificate from a trusted Certificate Authority (CA).

??? tip "Requires Extra"
    If you want to use certificate authentication, you need to install the `cert` extra:
    === "pip"
        ```shell
        pip install anaplan-sdk[cert]
        ```
    ===+ "uv"
        ```shell
        uv add anaplan-sdk[cert]
        ```
    === "Poetry"
        ```shell
        poetry add anaplan-sdk[cert]
        ```
    This will install [cryptography](https://github.com/pyca/cryptography) to securely construct the authentication request.

=== "Synchronous"
    ```python
    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
    )
    ```
=== "Asynchronous"
    ```python
    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.

An example for FastAPI is shown below, but you can use any other Web Framework.

??? tip "Requires Extra"
    If you want to use OAuth2 authentication, you need to install the `oauth` extra:
    === "pip"
        ```shell
        pip install anaplan-sdk[oauth]
        ```
    ===+ "uv"
        ```shell
        uv add anaplan-sdk[oauth]
        ```
    === "Poetry"
        ```shell
        poetry add anaplan-sdk[oauth]
        ```
    This will install [OAuthLib](https://oauthlib.readthedocs.io/en/latest/index.html) to securely construct the authentication request.


```python
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_url="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.

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


## OAuth for Local Applications

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

??? tip "Requires Extra"
    If you want to use OAuth2 authentication, you need to install the `oauth` extra:
    === "pip"
        ```shell
        pip install anaplan-sdk[oauth]
        ```
    ===+ "uv"
        ```shell
        uv add anaplan-sdk[oauth]
        ```
    === "Poetry"
        ```shell
        poetry add anaplan-sdk[oauth]
        ```
    This will install [OAuthLib](https://oauthlib.readthedocs.io/en/latest/index.html) to securely construct the authentication request.

=== "Synchronous"
    ```python
    anaplan = Client(
        auth=AnaplanOAuthCodeAuth(
            client_id=os.environ["OAUTH_CLIENT_ID"],
            client_secret=os.environ["OAUTH_CLIENT_SECRET"],
            redirect_url="https://vinzenzklass.github.io/anaplan-sdk",
        )
    )
    ```
=== "Asynchronous"
    ```python
    anaplan = AsyncClient(
        auth=AnaplanOAuthCodeAuth(
            client_id=os.environ["OAUTH_CLIENT_ID"],
            client_secret=os.environ["OAUTH_CLIENT_SECRET"],
            redirect_url="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.

???+ info "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](https://community.anaplan.com/discussion/156599/oauth-rediredt-url-port-for-desktop-apps).


## 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](https://www.python-httpx.org/advanced/authentication/#custom-authentication-schemes).

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.

```python
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. 
