Parent
Migration experience
A representative v2-to-v3 RC2 migration replaced scattered session checks with one application authorization policy shared by the client StackProvider and the server stack() configuration. The application identity included a role in addition to the standard id, name, email, and image fields.
At runtime this worked correctly. At authoring time, however, BTST widened the identity back to StackIdentity. Provider-specific fields are allowed through an index signature, but they become unknown. The migration therefore introduced an application identity extension and casts inside the permission policy to recover the role type.
That is an unfortunate place to lose type information: v3 centralizes authorization specifically so application policy can become easier to write, review, and reuse.
Problem
The identity type does not flow through the auth surface. A consumer can return a richer user from getIdentity, but the following public seams expose only the base StackIdentity:
- client and server auth providers;
- the
identity argument passed to can;
StackProvider's auth context;
useIdentity() and permission hooks/components; and
- auth bridges such as
createBetterAuthProvider.
As a result, custom claims such as roles, tenant ids, account status, or permission sets require assertions or local wrapper types. Those assertions can drift between client and server and weaken the benefit of sharing one policy.
The base identity remains valuable as a portable minimum contract. The missing piece is a backward-compatible way to specialize it once and retain that type end to end.
What to build
Add an application identity generic, inference path, or equivalent typed factory that preserves a consumer's StackIdentity extension through both client and server auth APIs.
The preferred experience is to declare or infer the identity once. The same concrete identity should then be visible in getIdentity, can, useIdentity, useCan/CanAccess policy evaluation, request identity helpers, and official auth adapters without repeated generic arguments or casts at every call site.
The design must retain the current non-generic API as a compatible default. This issue should not couple the core package to Better Auth's session type or to any particular role model.
Definition of success
A consumer with an identity such as { id: string; role: "user" | "admin"; tenantId: string } can implement one shared authorization policy. Both the client and server versions of that policy receive the exact identity type, including nullable unauthenticated state, without as, index-access narrowing, or duplicated wrapper interfaces.
A mismatch between the object returned by getIdentity and the configured application identity type should fail during typecheck.
Acceptance criteria
Blocked by
- None — can start immediately.
Parent
Migration experience
A representative v2-to-v3 RC2 migration replaced scattered session checks with one application authorization policy shared by the client
StackProviderand the serverstack()configuration. The application identity included a role in addition to the standard id, name, email, and image fields.At runtime this worked correctly. At authoring time, however, BTST widened the identity back to
StackIdentity. Provider-specific fields are allowed through an index signature, but they becomeunknown. The migration therefore introduced an application identity extension and casts inside the permission policy to recover the role type.That is an unfortunate place to lose type information: v3 centralizes authorization specifically so application policy can become easier to write, review, and reuse.
Problem
The identity type does not flow through the auth surface. A consumer can return a richer user from
getIdentity, but the following public seams expose only the baseStackIdentity:identityargument passed tocan;StackProvider's auth context;useIdentity()and permission hooks/components; andcreateBetterAuthProvider.As a result, custom claims such as roles, tenant ids, account status, or permission sets require assertions or local wrapper types. Those assertions can drift between client and server and weaken the benefit of sharing one policy.
The base identity remains valuable as a portable minimum contract. The missing piece is a backward-compatible way to specialize it once and retain that type end to end.
What to build
Add an application identity generic, inference path, or equivalent typed factory that preserves a consumer's
StackIdentityextension through both client and server auth APIs.The preferred experience is to declare or infer the identity once. The same concrete identity should then be visible in
getIdentity,can,useIdentity,useCan/CanAccesspolicy evaluation, request identity helpers, and official auth adapters without repeated generic arguments or casts at every call site.The design must retain the current non-generic API as a compatible default. This issue should not couple the core package to Better Auth's session type or to any particular role model.
Definition of success
A consumer with an identity such as
{ id: string; role: "user" | "admin"; tenantId: string }can implement one shared authorization policy. Both the client and server versions of that policy receive the exact identity type, including nullable unauthenticated state, withoutas, index-access narrowing, or duplicated wrapper interfaces.A mismatch between the object returned by
getIdentityand the configured application identity type should fail during typecheck.Acceptance criteria
getIdentityandcan.StackProvider,useIdentity,useCan, andCanAccessparticipate in the typed path without forcing repetitive annotations at each use site.nullremains the only unauthenticated identity state and is represented accurately throughout the generic API.StackIdentitycompile unchanged.Blocked by