Recipe: Plug In Your Existing User Table
Problem: your app already has a users table (or a user service) that other code depends
on. You want IAM’s authorization without migrating identity or maintaining two user lists.
1. Map your user to IamUserView
Section titled “1. Map your user to IamUserView”IamUserView is deliberately minimal — auth-relevant identity only. No password hash, no
roles: those stay IAM’s domain.
class AccountUserView(private val account: Account) : IamUserView { override val id: UUID get() = account.id override val tenantId: UUID get() = account.organizationId override val email: String get() = account.email override val firstName: String? get() = account.givenName override val lastName: String? get() = account.familyName override val isActive: Boolean get() = !account.suspended}2. Register an IamUserProvider bean
Section titled “2. Register an IamUserProvider bean”@Componentclass AccountUserProvider(private val accounts: AccountRepository) : IamUserProvider {
override fun findByTenantIdAndEmail(tenantId: UUID, email: String): IamUserView? = accounts.findByOrgAndEmail(tenantId, email)?.let(::AccountUserView)
override fun findById(id: UUID): IamUserView? = accounts.findById(id)?.let(::AccountUserView)}That’s it — the bean’s presence switches the mode. No configuration property.
What you get
Section titled “What you get”- Your table stays the system of record for identity. IAM resolves users through your
provider instead of its own
iam_userstable. - A shadow record is auto-created in
iam_userson first resolution, holding what IAM owns and you don’t: role assignments, 2FA state, refresh tokens. - Identity stays in sync — email and name are refreshed from your provider on each resolution, so a rename in your table propagates without a sync job.
- No bean, no change — without a provider IAM runs standalone against
iam_users, exactly as before.