Credentials
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 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 and 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:
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:
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:
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:
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:
@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
InputorOutputmodel - 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: how credentials arrive in handler requests and how to read them from
self.context - Inputs and outputs: how to declare typed
InputandOutputmodels for your apps and tasks