Skip to main content

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:

FieldPurpose
nameThe key under which the credential is stored
credential_typeWhich parser to use when resolving (for example "basic", "api_key")
store_nameWhich 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 functionResolves toUse for
basic_ref(name)BasicCredentialUsername + password
api_key_ref(name)ApiKeyCredentialAPI key with optional header name and prefix
bearer_token_ref(name)BearerTokenCredentialBearer token with optional expiry
oauth_client_ref(name)OAuthClientCredentialOAuth 2.0 client ID + secret + token URL
certificate_ref(name)CertificateCredentialmTLS / client certificate (PEM data)
atlan_api_token_ref(name)AtlanApiTokenAtlan API token + instance base URL
atlan_oauth_client_ref(name)AtlanOAuthClientAtlan 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 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: how credentials arrive in handler requests and how to read them from self.context
  • Inputs and outputs: how to declare typed Input and Output models for your apps and tasks