
Full-stack authentication stack: ready-to-run server plus shared SDK and client SDK with federated Google, passkey, OTP, email verification, KYC, reusable UI and credential manager.
Full-stack authentication and authorization solution for Kotlin Multiplatform (KMP) and Compose Multiplatform (CMP), featuring a Spring Boot Authorization Server and multiplatform client SDKs.
Your own auth server and a Compose Multiplatform login SDK, in one repo. Run one Docker image, add one Gradle dependency, and your Android, iOS, desktop and web apps sign users in with passwords, passkeys, Google, Apple or one-time codes, against a standard OAuth 2.1 / OpenID Connect server you host and own. No per-user pricing, and your users' data stays in your MongoDB.
git clone https://github.com/pitampoudel/komposeauth && cd komposeauth
cp .env.example .env && echo "BASE64_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env
docker compose up -d # then open http://localhost:8080/admin/config// commonMain
implementation("io.github.pitampoudel:komposeauth-client:<latest-version>")
val httpClient = HttpClient {
installKomposeAuth(
authServerUrl = "https://auth.example.com",
resourceServerUrls = listOf("https://api.example.com") // your APIs get the bearer token
)
}If komposeauth is useful to you, a ⭐ helps other Kotlin developers find it.
Most auth options for a Kotlin Multiplatform app make you pick between a hosted service that bills per active user and a generic identity server you have to wire into Compose yourself. komposeauth gives you both halves, built for each other:
| komposeauth | Hosted auth (Firebase Auth, Auth0, Clerk) | Keycloak and similar | |
|---|---|---|---|
| Who hosts it | You, from one Docker image | The vendor | You |
| Cost as you grow | Your server and database | Grows with monthly active users on paid tiers | Your server and database |
| Where user data lives | Your MongoDB | The vendor's cloud | Your database |
| KMP / Compose Multiplatform SDK | Ktor plugin, ViewModels and Compose UI for Android, iOS, desktop and web | Mostly per-platform SDKs, or community wrappers | None; you write the client side |
| Standards | OAuth 2.1, OpenID Connect, PKCE, JWKS | Proprietary APIs plus OIDC | OAuth 2.0, OpenID Connect, SAML |
| Any backend can verify tokens | Yes: RS256 JWTs checked against /oauth2/jwks
|
Yes | Yes |
Pick komposeauth when you are building a Kotlin Multiplatform or Compose Multiplatform app, want to own your users, and would rather not glue a generic identity server to your client by hand. Pick something else if you need SAML, enterprise federation, or a fully managed service you never operate.
komposeauth provides a complete, modern authentication ecosystem across server and client platforms:
komposeauth-shared): Cross-platform Kotlin Multiplatform data models, DTOs, and validation logic.komposeauth-client): Compose Multiplatform UI components, Ktor authentication plugins, Credential Manager integrations, and ViewModels for Android, iOS, Desktop, and Web.┌────────────────────────────────────────────────────────────────────────┐
│ Client Applications │
│ (Android, iOS, Desktop, Web / KMP & CMP) │
└──────────────┬──────────────────────────────────────────┬──────────────┘
│ │
│ 1. Sign in (OAuth 2.1 / PKCE / /login) │ 3. API Requests with Bearer JWT
▼ ▼
┌────────────────────────────────────────┐ ┌─────────────────────────┐
│ komposeauth Server (Auth Server) │ │ Consumer Resource Server│
│ - Issues RS256 JWTs │ │ (Your API Backend) │
│ - Hosted OIDC / Session Login │ │ - Validates JWT locally │
│ - Admin Console (/admin) │ │ - Reads user roles/UID │
│ - KYC & File Encryption │ └───────────┬─────────────┘
└──────────────┬───────────┬─────────────┘ │
│ │ │ 2. Fetch JWKS public keys
▼ ▼ │ (cached automatically)
┌─────────────────┐ ┌──────────────────┐ │
│ MongoDB Store │ │ S3 / GCS Storage │◄────────────┘
└─────────────────┘ └──────────────────┘
/.well-known/openid-configuration), and RS256 JWT token issuance validated via JWKS./admin/config) for runtime configuration of OAuth clients, SMTP, SMS providers, CORS, and rate limits.The fastest way to get started is using Docker Compose, which starts the server alongside a MongoDB instance:
git clone https://github.com/pitampoudel/komposeauth && cd komposeauth
cp .env.example .env
echo "BASE64_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env
docker compose up -dOpen http://localhost:8080/admin/config, enter the BASE64_ENCRYPTION_KEY from .env, and create the initial super admin account. Keep this key safe: it encrypts every secret the server stores.
To run the standalone Docker image against an existing MongoDB instance:
docker run -d -p 8080:8080 \
--name komposeauth \
-e MONGODB_URI="mongodb://your-mongo-host:27017" \
-e BASE64_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
-e SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER="http://localhost:8080" \
pitampoudel/komposeauth:latestOnce the container is running, navigate to the web administration console:
http://localhost:8080/admin/config
BASE64_ENCRYPTION_KEY into the form, or provide it via the X-Master-Key header:curl -H "X-Master-Key: <paste-your-base64-key>" http://localhost:8080/admin/configWhen no super admin exists yet (SUPER_ADMIN), the page opens with Create the first admin: enter an email and password (or the address of an account that already exists) to grant that account super admin privileges. Afterwards, sign in at /admin using your admin credentials instead of the master key. The initial setup form disappears once a super admin exists, and additional roles can be assigned from the Users management page.
Use the Admin Console to configure email (SMTP), SMS providers (Twilio, Infobip, WhatsApp), Google and Apple sign-in, error reporting and chat alert webhooks, custom branding, CORS origins, and rate limits. All third-party integrations are optional.
Profile photos, organization logos, and KYC documents are stored in buckets configured under Storage (/admin/storage) in the admin console. Any admin can view storage settings; only a SUPER_ADMIN can add or modify storage buckets. Note that asset uploads require an active storage bucket.
Supported storage backends include Google Cloud Storage (GCS) and any S3-compatible provider (Amazon S3, Cloudflare R2, MinIO, Backblaze B2, etc.):
{endpoint}/{bucket}/{name}) is supported.Public vs. Private Access:
users/ and organization_logos/ path prefixes.Bucket Migration:
rclone, aws s3 sync, or gcloud storage rsync) and link the old bucket entry using Files copied to… to preserve backward compatibility.| Variable | Required | Description | Default / Example |
|---|---|---|---|
MONGODB_URI |
Yes | MongoDB connection string (database defaults to komposeauth). |
mongodb://localhost:27017 |
BASE64_ENCRYPTION_KEY |
Yes | 32-byte Base64 AES key for encrypting secrets, KYC documents, and initial /admin/config access. |
Generated via openssl rand -base64 32
|
SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER |
Yes | Public origin URL of the auth server (used for tokens, discovery, and callbacks). Must omit trailing slash. |
https://auth.example.com or http://localhost
|
PORT |
No | Server HTTP port. | 8080 |
FORWARD_HEADERS_STRATEGY |
No | Reverse proxy header strategy (framework or none). |
framework |
SESSION_TIMEOUT |
No | Admin session timeout duration. | 24h |
SHUTDOWN_GRACE_PERIOD |
No | Graceful shutdown timeout. | 8s |
Set SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER to the public origin URL of your auth server (e.g. https://auth.example.com).
<ISSUER_URL>/login/oauth2/code/google as the authorized redirect URI in Google Cloud Console.Under Rate Limiting in /admin/config, configure client IP resolution depending on your deployment:
clientIpHeader to X-Real-IP, Fly-Client-IP, or CF-Connecting-IP.X-Forwarded-For platforms: Set trustedProxyCount to the number of reverse proxy hops you control (e.g., 1 for Google Cloud Run, 2 when behind an external load balancer).FORWARD_HEADERS_STRATEGY=none.Verify your detected IP address at any time using:
curl https://your-auth-server/admin/client-ip -H "Cookie: <your-session-cookie>"Every option runs the same image and keeps everything in MongoDB, so moving between them is a matter of pointing the new one at the same database with the same key and issuer. Where there is no MongoDB alongside, a MongoDB Atlas cluster works; the free M0 is enough to start. Allow the host's outbound addresses in Atlas's network access, or 0.0.0.0/0 on hosts whose addresses change.
| Platform | Deployment Method | Scales to zero | Memory Profile |
|---|---|---|---|
| Render |
render.yaml) |
Yes (Free plan) | 512 MB |
| Railway |
railway.json) |
Sleeps on idle (Hobby) | 512 MB |
| DigitalOcean |
.do/deploy.template.yaml) |
Configurable | 512 MB |
| Fly.io |
fly launch / fly deploy (fly.toml) |
Yes (auto stop/start) | 512 MB |
| Google Cloud Run |
gcloud run deploy command below |
Yes | 1 GiB |
| Self-hosted Docker |
docker compose up -d (Quickstart) |
No | 512 MB+ |
Deploy via the Blueprint button above or use render.yaml. The blueprint creates a web service from the published image and generates BASE64_ENCRYPTION_KEY; it asks only for MONGODB_URI. With no issuer set, the server uses its own onrender.com address. To serve it from your own domain, add the domain in Render and set SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER to it.
The blueprint sets BPL_JVM_THREAD_COUNT=50 and JAVA_TOOL_OPTIONS=-XX:ReservedCodeCacheSize=64M so the JVM fits in 512 MB.
Deploy using the Railway button above or connect your GitHub repo using railway.json:
MONGODB_URI).BASE64_ENCRYPTION_KEY: A 32-byte Base64 key (openssl rand -base64 32).SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER: https://${{RAILWAY_PUBLIC_DOMAIN}} or your custom domain./admin/config $\rightarrow$ Rate Limiting, set clientIpHeader to X-Real-IP.Deploy using the DigitalOcean button above or create a new App pointing to your repository (.do/deploy.template.yaml will be auto-detected):
MONGODB_URI to a MongoDB Atlas connection string.BASE64_ENCRYPTION_KEY is generated automatically; keep a copy somewhere safe.SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER to https://<your-app>.ondigitalocean.app or your custom domain.Deploy to Fly.io using the included fly.toml:
fly launch --no-deploy
fly secrets set \
MONGODB_URI="mongodb+srv://..." \
BASE64_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER="https://<your-app>.fly.dev"
fly deployUnder /admin/config $\rightarrow$ Rate Limiting, set clientIpHeader to Fly-Client-IP.
gcloud run deploy komposeauth \
--image pitampoudel/komposeauth:latest \
--set-env-vars MONGODB_URI="mongodb+srv://...",BASE64_ENCRYPTION_KEY="<your-base64-key>",SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER="https://auth.example.com" \
--min-instances 0 \
--concurrency 40 \
--cpu 1 --memory 1Gi \
--cpu-boost \
--startup-probe httpGet.path=/actuator/health/readiness,httpGet.port=8080,initialDelaySeconds=4,periodSeconds=2,timeoutSeconds=2,failureThreshold=45Under /admin/config $\rightarrow$ Rate Limiting, set trustedProxyCount to 1.
In the komposeauth architecture, your backend application is the OAuth 2.1 Resource Server. It hosts your business logic and protected REST/GraphQL/gRPC endpoints (e.g. https://api.example.com), while komposeauth serves exclusively as the Authorization Server (e.g. https://auth.example.com).
komposeauth and receives an RS256 JWT access token.resourceServerUrls, the client SDK attaches the token: Authorization: Bearer <jwt>.https://auth.example.com/oauth2/jwks (or OIDC discovery at /.well-known/openid-configuration).
komposeauth for each incoming request. The public keys are fetched once and cached.iss), expiration (exp), and audience/scopes are verified by your backend framework's standard JWT validator.sub), email, roles (roles), and custom claims directly from the verified JWT payload.Every access token issued by komposeauth contains standard JWT claims:
{
"iss": "https://auth.example.com",
"sub": "66f1c0e4a1b2c3d4e5f60718",
"aud": ["https://api.example.com"],
"exp": 1729000000,
"iat": 1728999100,
"scope": ["openid", "profile", "email"],
"roles": ["USER"]
}Any standard OAuth2/OIDC library can secure your backend:
install(Authentication) {
jwt("auth-jwt") {
realm = "komposeauth"
verifier(
JwkProviderBuilder(URL("https://auth.example.com/oauth2/jwks"))
.cached(10, 24, TimeUnit.HOURS)
.rateLimited(10, 1, TimeUnit.MINUTES)
.build(),
issuer = "https://auth.example.com"
) {
acceptLeeway(3)
}
validate { credential ->
if (credential.payload.subject.isNotEmpty()) {
JWTPrincipal(credential.payload)
} else null
}
}
}
routing {
authenticate("auth-jwt") {
get("/api/me") {
val principal = call.principal<JWTPrincipal>()
val userId = principal?.payload?.subject
call.respondText("Hello user: $userId")
}
}
}# application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com
jwk-set-uri: https://auth.example.com/oauth2/jwksimport { expressjwt } from "express-jwt";
import jwksRsa from "jwks-rsa";
const checkJwt = expressjwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksUri: "https://auth.example.com/oauth2/jwks"
}) as any,
issuer: "https://auth.example.com",
algorithms: ["RS256"]
});
app.get("/api/me", checkJwt, (req, res) => {
res.json({ user: req.auth });
});For complete examples in Python, Go, Fastify, and other frameworks, see the Full Integration Guide.
Add the dependencies to your Kotlin Multiplatform commonMain source set:
// build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
// Client SDK (includes UI components, ViewModels, and Ktor plugins)
implementation("io.github.pitampoudel:komposeauth-client:<latest-version>")
// Optional: Shared DTOs and validators (already transitive in client)
implementation("io.github.pitampoudel:komposeauth-shared:<latest-version>")
}
}
}Install the KomposeAuth plugin on your Ktor HttpClient:
val httpClient = HttpClient {
installKomposeAuth(
authServerUrl = "https://auth.example.com",
resourceServerUrls = listOf(
"https://api.example.com"
)
)
}
// Initialize the SDK with the configured client
initializeKomposeAuth(
httpClient = httpClient
)Platform Notes:
- Android: Pass the Android
Contextto store tokens encrypted with Android Keystore.- Desktop: Pass
appId(e.g. your application ID) to isolate token storage in Java Preferences.
@Composable
fun UserProfileScreen() {
val userState by rememberAuthenticatedUser()
userState?.let { user ->
Text("Welcome, ${user.displayName ?: user.email}")
} ?: Text("Not logged in")
}@Composable
fun LoginScreen(vm: LoginViewModel = koinViewModel()) {
val state by vm.state.collectAsStateWithLifecycle()
val credentialManager = rememberKmpCredentialManager()
LaunchedEffect(state.loginConfig) {
state.loginConfig?.let { config ->
when (val result = credentialManager.getCredential(config)) {
is Result.Success -> vm.onEvent(LoginEvent.Login(result.data))
is Result.Error -> vm.onEvent(LoginEvent.ShowInfoMsg(result.message))
}
}
}
}@Composable
fun OtpVerificationScreen(vm: OtpViewModel = koinViewModel()) {
registerSmsOtpRetriever { otpCode ->
vm.onEvent(OtpEvent.CodeChanged(otpCode))
}
}The SDK includes ready-to-use Compose Multiplatform UI components and utilities:
ScreenStateWrapper with error/info and loading progress dialogsCountryPicker, DateTimeField, and OTPTextField
rememberFilePicker for image/document selection(error).toStringRes()
A comprehensive integration guide for developers and LLM coding assistants covers frontend OAuth 2.1 PKCE flows, JWT claims structure, and backend token validation across multiple platforms (Spring Boot, Node.js, Python, Go, and Ktor):
llms.txt — High-level quick reference index (llms.txt standard)docs/llms-full.txt — Complete integration guideBoth files are also served directly by the server at https://<your-auth-server>/llms.txt and https://<your-auth-server>/llms-full.txt.
The project follows Semantic Versioning (vMAJOR.MINOR.PATCH).
Version resolution works uniformly across both local builds and CI:
-Pversion=X.Y.Z (explicit override)GITHUB_REF_NAME (e.g. v0.2.8 → 0.2.8)git describe --tags (e.g. v0.2.8 → 0.2.8)1.0.0-SNAPSHOT
CI publishes the image (.github/workflows/docker-publish.yml): a push to main publishes main and
latest, and a GitHub release vX.Y.Z publishes X.Y.Z and X.Y.
From IntelliJ, the shared run configurations in .run/ run the same commands:
bootBuildImage --publishImage to main and latest. Sign
in to Docker Hub once (Docker Desktop, or docker login).komposeauth service onto the new
image, keeping its environment. It deploys to your gcloud default project and asks for a region
if run/region isn't set.CI publishes the SDK (.github/workflows/library-publish.yml) to GitHub Packages and Maven Central.
From IntelliJ, publishToMavenLocal installs it into ~/.m2 for a local app, and Release SDK
to Maven Central tests and releases the version of the tag you are on (v0.2.8 → 0.2.8), with
mavenCentralUsername, mavenCentralPassword and the signingInMemoryKey* properties in
~/.gradle/gradle.properties.
The authorization server enforces browser origin security using the Sec-Fetch-Site header:
/admin/config.BASE64_ENCRYPTION_KEY before upload to cloud storage.GET /kyc/{userId}/documents/{front|back|selfie}) requiring a user bearer token or administrative session.Please report vulnerabilities privately, as described in SECURITY.md, rather than in a public issue.
Contributions are welcome. CONTRIBUTING.md covers running the server locally, the tests, and what a pull request needs. For changes that affect existing deployments, please open an issue first. What changed in each release is in CHANGELOG.md.
This project is licensed under the Apache License 2.0. See the LICENSE file for details.
Full-stack authentication and authorization solution for Kotlin Multiplatform (KMP) and Compose Multiplatform (CMP), featuring a Spring Boot Authorization Server and multiplatform client SDKs.
Your own auth server and a Compose Multiplatform login SDK, in one repo. Run one Docker image, add one Gradle dependency, and your Android, iOS, desktop and web apps sign users in with passwords, passkeys, Google, Apple or one-time codes, against a standard OAuth 2.1 / OpenID Connect server you host and own. No per-user pricing, and your users' data stays in your MongoDB.
git clone https://github.com/pitampoudel/komposeauth && cd komposeauth
cp .env.example .env && echo "BASE64_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env
docker compose up -d # then open http://localhost:8080/admin/config// commonMain
implementation("io.github.pitampoudel:komposeauth-client:<latest-version>")
val httpClient = HttpClient {
installKomposeAuth(
authServerUrl = "https://auth.example.com",
resourceServerUrls = listOf("https://api.example.com") // your APIs get the bearer token
)
}If komposeauth is useful to you, a ⭐ helps other Kotlin developers find it.
Most auth options for a Kotlin Multiplatform app make you pick between a hosted service that bills per active user and a generic identity server you have to wire into Compose yourself. komposeauth gives you both halves, built for each other:
| komposeauth | Hosted auth (Firebase Auth, Auth0, Clerk) | Keycloak and similar | |
|---|---|---|---|
| Who hosts it | You, from one Docker image | The vendor | You |
| Cost as you grow | Your server and database | Grows with monthly active users on paid tiers | Your server and database |
| Where user data lives | Your MongoDB | The vendor's cloud | Your database |
| KMP / Compose Multiplatform SDK | Ktor plugin, ViewModels and Compose UI for Android, iOS, desktop and web | Mostly per-platform SDKs, or community wrappers | None; you write the client side |
| Standards | OAuth 2.1, OpenID Connect, PKCE, JWKS | Proprietary APIs plus OIDC | OAuth 2.0, OpenID Connect, SAML |
| Any backend can verify tokens | Yes: RS256 JWTs checked against /oauth2/jwks
|
Yes | Yes |
Pick komposeauth when you are building a Kotlin Multiplatform or Compose Multiplatform app, want to own your users, and would rather not glue a generic identity server to your client by hand. Pick something else if you need SAML, enterprise federation, or a fully managed service you never operate.
komposeauth provides a complete, modern authentication ecosystem across server and client platforms:
komposeauth-shared): Cross-platform Kotlin Multiplatform data models, DTOs, and validation logic.komposeauth-client): Compose Multiplatform UI components, Ktor authentication plugins, Credential Manager integrations, and ViewModels for Android, iOS, Desktop, and Web.┌────────────────────────────────────────────────────────────────────────┐
│ Client Applications │
│ (Android, iOS, Desktop, Web / KMP & CMP) │
└──────────────┬──────────────────────────────────────────┬──────────────┘
│ │
│ 1. Sign in (OAuth 2.1 / PKCE / /login) │ 3. API Requests with Bearer JWT
▼ ▼
┌────────────────────────────────────────┐ ┌─────────────────────────┐
│ komposeauth Server (Auth Server) │ │ Consumer Resource Server│
│ - Issues RS256 JWTs │ │ (Your API Backend) │
│ - Hosted OIDC / Session Login │ │ - Validates JWT locally │
│ - Admin Console (/admin) │ │ - Reads user roles/UID │
│ - KYC & File Encryption │ └───────────┬─────────────┘
└──────────────┬───────────┬─────────────┘ │
│ │ │ 2. Fetch JWKS public keys
▼ ▼ │ (cached automatically)
┌─────────────────┐ ┌──────────────────┐ │
│ MongoDB Store │ │ S3 / GCS Storage │◄────────────┘
└─────────────────┘ └──────────────────┘
/.well-known/openid-configuration), and RS256 JWT token issuance validated via JWKS./admin/config) for runtime configuration of OAuth clients, SMTP, SMS providers, CORS, and rate limits.The fastest way to get started is using Docker Compose, which starts the server alongside a MongoDB instance:
git clone https://github.com/pitampoudel/komposeauth && cd komposeauth
cp .env.example .env
echo "BASE64_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env
docker compose up -dOpen http://localhost:8080/admin/config, enter the BASE64_ENCRYPTION_KEY from .env, and create the initial super admin account. Keep this key safe: it encrypts every secret the server stores.
To run the standalone Docker image against an existing MongoDB instance:
docker run -d -p 8080:8080 \
--name komposeauth \
-e MONGODB_URI="mongodb://your-mongo-host:27017" \
-e BASE64_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
-e SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER="http://localhost:8080" \
pitampoudel/komposeauth:latestOnce the container is running, navigate to the web administration console:
http://localhost:8080/admin/config
BASE64_ENCRYPTION_KEY into the form, or provide it via the X-Master-Key header:curl -H "X-Master-Key: <paste-your-base64-key>" http://localhost:8080/admin/configWhen no super admin exists yet (SUPER_ADMIN), the page opens with Create the first admin: enter an email and password (or the address of an account that already exists) to grant that account super admin privileges. Afterwards, sign in at /admin using your admin credentials instead of the master key. The initial setup form disappears once a super admin exists, and additional roles can be assigned from the Users management page.
Use the Admin Console to configure email (SMTP), SMS providers (Twilio, Infobip, WhatsApp), Google and Apple sign-in, error reporting and chat alert webhooks, custom branding, CORS origins, and rate limits. All third-party integrations are optional.
Profile photos, organization logos, and KYC documents are stored in buckets configured under Storage (/admin/storage) in the admin console. Any admin can view storage settings; only a SUPER_ADMIN can add or modify storage buckets. Note that asset uploads require an active storage bucket.
Supported storage backends include Google Cloud Storage (GCS) and any S3-compatible provider (Amazon S3, Cloudflare R2, MinIO, Backblaze B2, etc.):
{endpoint}/{bucket}/{name}) is supported.Public vs. Private Access:
users/ and organization_logos/ path prefixes.Bucket Migration:
rclone, aws s3 sync, or gcloud storage rsync) and link the old bucket entry using Files copied to… to preserve backward compatibility.| Variable | Required | Description | Default / Example |
|---|---|---|---|
MONGODB_URI |
Yes | MongoDB connection string (database defaults to komposeauth). |
mongodb://localhost:27017 |
BASE64_ENCRYPTION_KEY |
Yes | 32-byte Base64 AES key for encrypting secrets, KYC documents, and initial /admin/config access. |
Generated via openssl rand -base64 32
|
SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER |
Yes | Public origin URL of the auth server (used for tokens, discovery, and callbacks). Must omit trailing slash. |
https://auth.example.com or http://localhost
|
PORT |
No | Server HTTP port. | 8080 |
FORWARD_HEADERS_STRATEGY |
No | Reverse proxy header strategy (framework or none). |
framework |
SESSION_TIMEOUT |
No | Admin session timeout duration. | 24h |
SHUTDOWN_GRACE_PERIOD |
No | Graceful shutdown timeout. | 8s |
Set SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER to the public origin URL of your auth server (e.g. https://auth.example.com).
<ISSUER_URL>/login/oauth2/code/google as the authorized redirect URI in Google Cloud Console.Under Rate Limiting in /admin/config, configure client IP resolution depending on your deployment:
clientIpHeader to X-Real-IP, Fly-Client-IP, or CF-Connecting-IP.X-Forwarded-For platforms: Set trustedProxyCount to the number of reverse proxy hops you control (e.g., 1 for Google Cloud Run, 2 when behind an external load balancer).FORWARD_HEADERS_STRATEGY=none.Verify your detected IP address at any time using:
curl https://your-auth-server/admin/client-ip -H "Cookie: <your-session-cookie>"Every option runs the same image and keeps everything in MongoDB, so moving between them is a matter of pointing the new one at the same database with the same key and issuer. Where there is no MongoDB alongside, a MongoDB Atlas cluster works; the free M0 is enough to start. Allow the host's outbound addresses in Atlas's network access, or 0.0.0.0/0 on hosts whose addresses change.
| Platform | Deployment Method | Scales to zero | Memory Profile |
|---|---|---|---|
| Render |
render.yaml) |
Yes (Free plan) | 512 MB |
| Railway |
railway.json) |
Sleeps on idle (Hobby) | 512 MB |
| DigitalOcean |
.do/deploy.template.yaml) |
Configurable | 512 MB |
| Fly.io |
fly launch / fly deploy (fly.toml) |
Yes (auto stop/start) | 512 MB |
| Google Cloud Run |
gcloud run deploy command below |
Yes | 1 GiB |
| Self-hosted Docker |
docker compose up -d (Quickstart) |
No | 512 MB+ |
Deploy via the Blueprint button above or use render.yaml. The blueprint creates a web service from the published image and generates BASE64_ENCRYPTION_KEY; it asks only for MONGODB_URI. With no issuer set, the server uses its own onrender.com address. To serve it from your own domain, add the domain in Render and set SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER to it.
The blueprint sets BPL_JVM_THREAD_COUNT=50 and JAVA_TOOL_OPTIONS=-XX:ReservedCodeCacheSize=64M so the JVM fits in 512 MB.
Deploy using the Railway button above or connect your GitHub repo using railway.json:
MONGODB_URI).BASE64_ENCRYPTION_KEY: A 32-byte Base64 key (openssl rand -base64 32).SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER: https://${{RAILWAY_PUBLIC_DOMAIN}} or your custom domain./admin/config $\rightarrow$ Rate Limiting, set clientIpHeader to X-Real-IP.Deploy using the DigitalOcean button above or create a new App pointing to your repository (.do/deploy.template.yaml will be auto-detected):
MONGODB_URI to a MongoDB Atlas connection string.BASE64_ENCRYPTION_KEY is generated automatically; keep a copy somewhere safe.SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER to https://<your-app>.ondigitalocean.app or your custom domain.Deploy to Fly.io using the included fly.toml:
fly launch --no-deploy
fly secrets set \
MONGODB_URI="mongodb+srv://..." \
BASE64_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER="https://<your-app>.fly.dev"
fly deployUnder /admin/config $\rightarrow$ Rate Limiting, set clientIpHeader to Fly-Client-IP.
gcloud run deploy komposeauth \
--image pitampoudel/komposeauth:latest \
--set-env-vars MONGODB_URI="mongodb+srv://...",BASE64_ENCRYPTION_KEY="<your-base64-key>",SPRING_SECURITY_OAUTH2_AUTHORIZATIONSERVER_ISSUER="https://auth.example.com" \
--min-instances 0 \
--concurrency 40 \
--cpu 1 --memory 1Gi \
--cpu-boost \
--startup-probe httpGet.path=/actuator/health/readiness,httpGet.port=8080,initialDelaySeconds=4,periodSeconds=2,timeoutSeconds=2,failureThreshold=45Under /admin/config $\rightarrow$ Rate Limiting, set trustedProxyCount to 1.
In the komposeauth architecture, your backend application is the OAuth 2.1 Resource Server. It hosts your business logic and protected REST/GraphQL/gRPC endpoints (e.g. https://api.example.com), while komposeauth serves exclusively as the Authorization Server (e.g. https://auth.example.com).
komposeauth and receives an RS256 JWT access token.resourceServerUrls, the client SDK attaches the token: Authorization: Bearer <jwt>.https://auth.example.com/oauth2/jwks (or OIDC discovery at /.well-known/openid-configuration).
komposeauth for each incoming request. The public keys are fetched once and cached.iss), expiration (exp), and audience/scopes are verified by your backend framework's standard JWT validator.sub), email, roles (roles), and custom claims directly from the verified JWT payload.Every access token issued by komposeauth contains standard JWT claims:
{
"iss": "https://auth.example.com",
"sub": "66f1c0e4a1b2c3d4e5f60718",
"aud": ["https://api.example.com"],
"exp": 1729000000,
"iat": 1728999100,
"scope": ["openid", "profile", "email"],
"roles": ["USER"]
}Any standard OAuth2/OIDC library can secure your backend:
install(Authentication) {
jwt("auth-jwt") {
realm = "komposeauth"
verifier(
JwkProviderBuilder(URL("https://auth.example.com/oauth2/jwks"))
.cached(10, 24, TimeUnit.HOURS)
.rateLimited(10, 1, TimeUnit.MINUTES)
.build(),
issuer = "https://auth.example.com"
) {
acceptLeeway(3)
}
validate { credential ->
if (credential.payload.subject.isNotEmpty()) {
JWTPrincipal(credential.payload)
} else null
}
}
}
routing {
authenticate("auth-jwt") {
get("/api/me") {
val principal = call.principal<JWTPrincipal>()
val userId = principal?.payload?.subject
call.respondText("Hello user: $userId")
}
}
}# application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com
jwk-set-uri: https://auth.example.com/oauth2/jwksimport { expressjwt } from "express-jwt";
import jwksRsa from "jwks-rsa";
const checkJwt = expressjwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksUri: "https://auth.example.com/oauth2/jwks"
}) as any,
issuer: "https://auth.example.com",
algorithms: ["RS256"]
});
app.get("/api/me", checkJwt, (req, res) => {
res.json({ user: req.auth });
});For complete examples in Python, Go, Fastify, and other frameworks, see the Full Integration Guide.
Add the dependencies to your Kotlin Multiplatform commonMain source set:
// build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
// Client SDK (includes UI components, ViewModels, and Ktor plugins)
implementation("io.github.pitampoudel:komposeauth-client:<latest-version>")
// Optional: Shared DTOs and validators (already transitive in client)
implementation("io.github.pitampoudel:komposeauth-shared:<latest-version>")
}
}
}Install the KomposeAuth plugin on your Ktor HttpClient:
val httpClient = HttpClient {
installKomposeAuth(
authServerUrl = "https://auth.example.com",
resourceServerUrls = listOf(
"https://api.example.com"
)
)
}
// Initialize the SDK with the configured client
initializeKomposeAuth(
httpClient = httpClient
)Platform Notes:
- Android: Pass the Android
Contextto store tokens encrypted with Android Keystore.- Desktop: Pass
appId(e.g. your application ID) to isolate token storage in Java Preferences.
@Composable
fun UserProfileScreen() {
val userState by rememberAuthenticatedUser()
userState?.let { user ->
Text("Welcome, ${user.displayName ?: user.email}")
} ?: Text("Not logged in")
}@Composable
fun LoginScreen(vm: LoginViewModel = koinViewModel()) {
val state by vm.state.collectAsStateWithLifecycle()
val credentialManager = rememberKmpCredentialManager()
LaunchedEffect(state.loginConfig) {
state.loginConfig?.let { config ->
when (val result = credentialManager.getCredential(config)) {
is Result.Success -> vm.onEvent(LoginEvent.Login(result.data))
is Result.Error -> vm.onEvent(LoginEvent.ShowInfoMsg(result.message))
}
}
}
}@Composable
fun OtpVerificationScreen(vm: OtpViewModel = koinViewModel()) {
registerSmsOtpRetriever { otpCode ->
vm.onEvent(OtpEvent.CodeChanged(otpCode))
}
}The SDK includes ready-to-use Compose Multiplatform UI components and utilities:
ScreenStateWrapper with error/info and loading progress dialogsCountryPicker, DateTimeField, and OTPTextField
rememberFilePicker for image/document selection(error).toStringRes()
A comprehensive integration guide for developers and LLM coding assistants covers frontend OAuth 2.1 PKCE flows, JWT claims structure, and backend token validation across multiple platforms (Spring Boot, Node.js, Python, Go, and Ktor):
llms.txt — High-level quick reference index (llms.txt standard)docs/llms-full.txt — Complete integration guideBoth files are also served directly by the server at https://<your-auth-server>/llms.txt and https://<your-auth-server>/llms-full.txt.
The project follows Semantic Versioning (vMAJOR.MINOR.PATCH).
Version resolution works uniformly across both local builds and CI:
-Pversion=X.Y.Z (explicit override)GITHUB_REF_NAME (e.g. v0.2.8 → 0.2.8)git describe --tags (e.g. v0.2.8 → 0.2.8)1.0.0-SNAPSHOT
CI publishes the image (.github/workflows/docker-publish.yml): a push to main publishes main and
latest, and a GitHub release vX.Y.Z publishes X.Y.Z and X.Y.
From IntelliJ, the shared run configurations in .run/ run the same commands:
bootBuildImage --publishImage to main and latest. Sign
in to Docker Hub once (Docker Desktop, or docker login).komposeauth service onto the new
image, keeping its environment. It deploys to your gcloud default project and asks for a region
if run/region isn't set.CI publishes the SDK (.github/workflows/library-publish.yml) to GitHub Packages and Maven Central.
From IntelliJ, publishToMavenLocal installs it into ~/.m2 for a local app, and Release SDK
to Maven Central tests and releases the version of the tag you are on (v0.2.8 → 0.2.8), with
mavenCentralUsername, mavenCentralPassword and the signingInMemoryKey* properties in
~/.gradle/gradle.properties.
The authorization server enforces browser origin security using the Sec-Fetch-Site header:
/admin/config.BASE64_ENCRYPTION_KEY before upload to cloud storage.GET /kyc/{userId}/documents/{front|back|selfie}) requiring a user bearer token or administrative session.Please report vulnerabilities privately, as described in SECURITY.md, rather than in a public issue.
Contributions are welcome. CONTRIBUTING.md covers running the server locally, the tests, and what a pull request needs. For changes that affect existing deployments, please open an issue first. What changed in each release is in CHANGELOG.md.
This project is licensed under the Apache License 2.0. See the LICENSE file for details.