
## Credentials

URL: https://docs.atlan.com/product/capabilities/build-apps/concepts/credentials

> Understand how the App Framework keeps secrets out of your code using typed credential references—declare, resolve, and access credentials safely in tasks and handlers at runtime.

Credentials in the App Framework are never stored in your code or passed as plain text. Instead, Atlan keeps secrets in secure credential storage and gives your app a **credential reference**: a lightweight, typed identifier that contains no sensitive data. At runtime, you call `self.context.resolve_credential()` to exchange the reference for the actual credential object. The framework handles retrieval; you handle the value.

This design means credential references are safe to include in `Input` and `Output` models, log, and pass between tasks—they identify a credential without exposing it.

## Credential references

A `CredentialRef` is a frozen Pydantic model with three main fields:

| Field | Purpose |
|---|---|
| `name` | The key under which the credential is stored |
| `credential_type` | Which parser to use when resolving (for example `"basic"`, `"api_key"`) |
| `store_name` | Which secret store to read from (default: `"default"`) |

You rarely construct `CredentialRef` directly. Instead, use a factory function that sets the correct `credential_type` for you.

## Available credential types

| Factory function | Resolves to | Use for |
|---|---|---|
| `basic_ref(name)` | `BasicCredential` | Username + password |
| `api_key_ref(name)` | `ApiKeyCredential` | API key with optional header name and prefix |
| `bearer_token_ref(name)` | `BearerTokenCredential` | Bearer token with optional expiry |
| `oauth_client_ref(name)` | `OAuthClientCredential` | OAuth 2.0 client ID + secret + token URL |
| `certificate_ref(name)` | `CertificateCredential` | mTLS / client certificate (PEM data) |
| `atlan_api_token_ref(name)` | `AtlanApiToken` | Atlan API token + instance base URL |
| `atlan_oauth_client_ref(name)` | `AtlanOAuthClient` | Atlan OAuth client credential + instance base URL |

All factory functions accept an optional `store_name` keyword argument for multi-store setups.

These are the most common credential types. For the full list, see [`application_sdk/credentials/ref.py`](https://github.com/atlanhq/application-sdk/blob/main/application_sdk/credentials/ref.py) in the repository.

### Resolved credential fields

Each resolved type exposes typed fields—no dict lookups required:

**`BasicCredential`**: `.username`, `.password`

**`ApiKeyCredential`**: `.api_key`, `.header_name` (default `"X-API-Key"`), `.prefix`

**`BearerTokenCredential`**: `.token`, `.expires_at` (ISO-8601 string), `.is_expired()` method

**`OAuthClientCredential`**: `.client_id`, `.client_secret`, `.token_url`, `.scopes`, `.access_token`, `.refresh_token`, `.expires_at`

**`CertificateCredential`**: `.cert_data`, `.key_data`, `.ca_data`, `.passphrase` (all PEM-encoded strings)

**`AtlanApiToken`**: extends `BearerTokenCredential`, adds `.base_url`

**`AtlanOAuthClient`**: extends `OAuthClientCredential`, adds `.base_url`

These are the most common fields. For the canonical field definitions, see [`application_sdk/credentials/types.py`](https://github.com/atlanhq/application-sdk/blob/main/application_sdk/credentials/types.py) and [`application_sdk/credentials/atlan.py`](https://github.com/atlanhq/application-sdk/blob/main/application_sdk/credentials/atlan.py) in the repository.

## Receive credentials as input

Declare the credential on your `Input` model using the factory function as both the type annotation and the default:

```python
from application_sdk.credentials import basic_ref
from application_sdk.contracts import Input

class ConnectInput(Input):
 credential: basic_ref("my-db") = basic_ref("my-db").default()
```

This tells the framework: when this input arrives, expect a reference to the credential stored under the key `"my-db"`, and resolve it as a `BasicCredential`.

The same pattern works for any credential type:

```python
from application_sdk.credentials import api_key_ref, oauth_client_ref
from application_sdk.contracts import Input

class ApiConnectInput(Input):
 credential: api_key_ref("my-api") = api_key_ref("my-api").default()

class OAuthConnectInput(Input):
 credential: oauth_client_ref("my-oauth") = oauth_client_ref("my-oauth").default()
```

## Resolve credentials

Call `self.context.resolve_credential()` inside a `@task` method to exchange the reference for the live credential object:

```python
from application_sdk.app import App, task
from application_sdk.credentials import basic_ref
from application_sdk.contracts import Input, Output

class ConnectInput(Input):
 credential: basic_ref("my-db") = basic_ref("my-db").default()

class ConnectOutput(Output):
 connected: bool = False

class MyApp(App):
 @task(timeout_seconds=30)
 async def connect(self, input: ConnectInput) -> ConnectOutput:
 cred = await self.context.resolve_credential(input.credential)
 # cred is a BasicCredential with .username and .password
 async with create_connection(cred.username, cred.password) as conn:
 await conn.execute("SELECT 1")
 return ConnectOutput(connected=True)
```

The resolved credential object is typed—your IDE can autocomplete `.username`, `.api_key`, `.token`, etc. without casting.

## Access Atlan

For apps that interact directly with Atlan, mix in `AtlanClientMixin` to get a cached `AsyncAtlanClient`:

```python
from application_sdk.app import App, task
from application_sdk.contracts import Input, Output
from application_sdk.credentials import AtlanClientMixin, atlan_api_token_ref

class FetchInput(Input):
 credential: atlan_api_token_ref("atlan-token") = atlan_api_token_ref("atlan-token").default()

class FetchOutput(Output):
 asset_count: int = 0

class MyApp(App, AtlanClientMixin):
 @task(timeout_seconds=60)
 async def fetch_assets(self, input: FetchInput) -> FetchOutput:
 client = await self.get_or_create_async_atlan_client(input.credential)
 results = await client.asset.search(...)
 return FetchOutput(asset_count=len(results))
```

`get_or_create_async_atlan_client` resolves the credential on the first call and caches the client for subsequent calls within the same execution. It works with both `AtlanApiToken` and `AtlanOAuthClient` credentials.

## Read raw secrets

For secrets not tied to a typed credential—configuration values, signing keys, internal tokens—use `self.context.get_secret`:

```python
@task
async def sign_payload(self, input: SignInput) -> SignOutput:
 signing_key = await self.context.get_secret("my-signing-key")
 # signing_key is a raw string from the framework's secret store
 ...
```

`get_secret` is also available inside Handler methods through the same `self.context` interface.

## Security properties

**References contain no secrets.** A `CredentialRef` holds only a name, a type, and a store identifier. You can safely:

- Declare it as a field on an `Input` or `Output` model
- Pass it from one task to another
- Include it in structured log output

**Resolution happens at runtime, inside a task.** The secret value is never present in your app's code, configuration, or log output. If you need to confirm a credential is in place without resolving it, check `input.credential.name`: that's always safe to log.

**Never hard-code credential values.** The correct pattern is always a `CredentialRef` in your `Input` model plus `resolve_credential()` at the point of use. Hard-coded secrets bypass secure storage and can't be rotated without a code change. Enforce a CI check that flags any such mistakes.

## See also

- [Handlers](https://docs.atlan.com/llms/platform/build-apps/handlers/llms.txt): how credentials arrive in handler requests and how to read them from `self.context`
- [Inputs and outputs](https://docs.atlan.com/llms/platform/build-apps/inputs-and-outputs/llms.txt): how to declare typed `Input` and `Output` models for your apps and tasks

---
