Atmosphere Credential Provider for Android #
This repository contains an experimental Android app that makes an existing AT Protocol account available as a device account. It runs on stock Android 14 or newer and does not require an Android or GrapheneOS fork.
The project contains three small modules:
providersigns in through AT Protocol OAuth, encrypts its session with an Android Keystore key, registers the public account identity with AndroidAccountManager, and offers that identity through Credential Manager.atmosphere-credentialdefines the versioned custom credential used by the provider and participating apps.sample-appshows the complete request and decode flow.
The credential contains only the selected account's DID, handle, PDS origin, and primary-account flag. The provider's access token, refresh token, and DPoP private key never cross the app boundary. A consuming app must create and own a separate OAuth grant.
Requirements #
- JDK 17 or newer, using Android Studio or command-line Gradle
- Android SDK 37 for the provider and sample app. The credential contract compiles against SDK 36.
- Android 14 or newer for the provider and picker
- Public AT Protocol native-client metadata served over HTTPS and an exact matching callback URI
- A PDS implementing the experimental
/oauth/device-approval/localextension for the optional passwordless, same-device flow
The checked-in defaults reproduce the prototype using:
- client ID:
https://auth.lanoue.dev/oauth/comail-os/client-metadata.json - redirect URI:
dev.lanoue.auth:/oauth/callback
Override them for another OAuth client identity:
atmosphereOAuthClientId=https://example.com/oauth/client-metadata.json
atmosphereOAuthRedirectUri=com.example:/oauth/callback
The client ID must be an HTTPS URL without an explicit port. The redirect URI
must use the reversed client hostname as its custom scheme, followed by one
slash and a non-empty path. It cannot contain a query or fragment. For example,
https://example.com/... pairs with com.example:/....
Put these values in your user-level Gradle properties file or pass them with
-P. They are public configuration and are embedded in the APK. Never commit
signing material or actual secrets.
Build #
./gradlew --dependency-verification strict test lint assembleDebug
Install provider/build/outputs/apk/debug/provider-debug.apk and sign in with an
AT Protocol handle, a did:plc, or a hostname-level did:web. Android may ask
you to enable the provider in Credential Manager settings before another app can
request it.
See Adding support to an AT Protocol app for the consumer-side code.
Remove an account #
Open Atmosphere accounts and tap Remove from this device, or remove the account from Android's account settings. Either path removes the Android account and deletes the provider's local OAuth tokens and DPoP key.
Removing the device account does not sign out apps that already received their own OAuth grants, and it does not delete the AT Protocol account or any PDS data. The provider does not call the PDS revocation endpoint during removal. Deleting the DPoP key means the provider can no longer use that grant, though the PDS may retain the grant record until it expires or the user revokes it there.
Security boundary #
- AT Protocol OAuth uses PAR, PKCE, and DPoP with server-issued nonces.
- The token response
submust match the DID resolved before authorization. - Pending OAuth state and provider sessions are AES-GCM encrypted with a non-exportable Android Keystore key.
- Each OAuth session has a separate non-exportable P-256 DPoP key.
AccountManagerand the custom credential carry public identity metadata only. They never contain OAuth tokens.- Selecting a device account does not authorize PDS access. The consuming app must run its own OAuth flow and request its own scopes.
- The optional same-device handler accepts only the exact enrolled PDS or authorization-server origin. It uses the provider's DPoP-bound session to authenticate the browser sign-in. The PDS then asks the user to approve the app's scopes and issues a separate grant.
The provider stores its initial OAuth session as encrypted device state. This session never leaves the provider. The optional same-device handler refreshes it when needed, but only while talking to the matching account server.
The passwordless handler uses the experimental
/oauth/device-approval/local PDS endpoint from this prototype. It is not part
of Android Credential Manager or the current AT Protocol OAuth specification.
Without that PDS extension, Credential Manager still solves account discovery,
but the user completes the PDS's normal authentication and consent screens.
Status #
This is a research prototype without a professional security audit. The repository does not distribute a compiled APK.
License #
The provider and repository-level project files use the GNU Affero General Public License v3.0 or later. The credential contract, sample app, and integration guide use the Apache License 2.0 so other apps can adopt the contract without adopting the provider's license. See the license map for the exact paths.