Skip to content

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.

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
}
@Component
class 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.

  • Your table stays the system of record for identity. IAM resolves users through your provider instead of its own iam_users table.
  • A shadow record is auto-created in iam_users on 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.