ngx-better-auth

June 24, 2026 ยท View on GitHub

An Angular 20+ wrapper for Better Auth. Provides reactive session handling with signals, clean DI provider setup with observables, and modern guards.

npm npm bundle size license downloads

angular better-auth


๐Ÿš€ Compatibility

ngx-better-authAngularBetter Auth
1.6.x>=20>=1.6.10 <1.7.0
0.11.x>=20>=1.3.7

๐Ÿ“ฆ Installation

npm install ngx-better-auth better-auth

โš™๏ธ Setup Provider

First, configure your Better Auth client in your application:

// app.config.ts
import { ApplicationConfig } from '@angular/core'
import { provideBetterAuth } from 'ngx-better-auth'
import { environment } from './environments/environment'
import { adminClient, siweClient, twoFactorClient, usernameClient } from 'better-auth/client/plugins'
import { passkeyClient } from '@better-auth/passkey/client'
import { stripeClient } from '@better-auth/stripe/client'

export const appConfig: ApplicationConfig = {
  providers: [
    provideBetterAuth({
      baseURL: environment.apiUrl, // it works also with proxy config
      basePath: '/auth',   // optional, default is '/api/auth'
        
      // Example with plugins
      plugins: [
        usernameClient(),
        twoFactorClient({
          onTwoFactorRedirect() {
              window.location.href = '/two-factor-auth'
          },
        }),
        adminClient({
          ac: accessControl,
          roles: {
            admin,
            moderator,
            user,
          },
        }),
        passkeyClient(),
        stripeClient({ subscription: true }),
        siweClient(),
      ],
    })
  ]
}

๐Ÿงฉ Different services

Migrating to 1.6.x

ngx-better-auth 1.6.x targets Better Auth >=1.6.10 <1.7.0.

Optional plugin services can be imported from secondary entrypoints when they rely on optional peer dependencies:

import { AuthService, provideBetterAuth } from 'ngx-better-auth'

import { ApiKeyService } from 'ngx-better-auth/api-key'
import { OAuthProviderService } from 'ngx-better-auth/oauth-provider'
import { PasskeyService } from 'ngx-better-auth/passkey'
import { ScimService } from 'ngx-better-auth/scim'
import { SsoService } from 'ngx-better-auth/sso'
import { StripeService } from 'ngx-better-auth/stripe'

๐Ÿ”Œ Plugin compatibility

Authentication

  • โœ… Two Factor โžก๏ธ TwoFactorService
  • โœ… Username โžก๏ธ UsernameService
  • โœ… Anonymous โžก๏ธ AnonymousService
  • โœ… Phone Number โžก๏ธ PhoneNumberService
  • โœ… Magic Link โžก๏ธ MagicLinkService
  • โœ… Email OTP โžก๏ธ EmailOtpService
  • โœ… Passkey โžก๏ธ PasskeyService from ngx-better-auth/passkey
  • โœ… Generic OAuth โžก๏ธ GenericOauthService
  • โœ… OAuth Popup โžก๏ธ OauthPopupService
  • โœ… One Tap โžก๏ธ OneTapService
  • โœ… Sign In With Ethereum (SIWE) โžก๏ธ SiweService

Authorization & Management

  • โœ… Admin โžก๏ธ AdminService
  • โœ… Organization โžก๏ธ OrganizationService

API & Tokens

  • โœ… Last Login Method โžก๏ธ LastLoginMethodService
  • โœ… Multi Session โžก๏ธ MultiSessionService
  • โœ… One Time Token โžก๏ธ OneTimeTokenService
  • โœ… JWT โžก๏ธ JwtService
  • โœ… Bearer โžก๏ธ BearerService, bearerHeaders(), bearerFetchOptions()
  • โœ… API Key โžก๏ธ ApiKeyService from ngx-better-auth/api-key

OAuth & OIDC Providers

  • โœ… Device Authorization โžก๏ธ DeviceAuthorizationService
  • โœ… OAuth 2.1 Provider โžก๏ธ OAuthProviderService from ngx-better-auth/oauth-provider
  • โœ… SSO โžก๏ธ SsoService from ngx-better-auth/sso

Payments & Billing

  • โœ… Stripe โžก๏ธ StripeService from ngx-better-auth/stripe

Security & Utilities

  • โœ… Captcha โžก๏ธ captchaHeaders(), captchaFetchOptions() for the x-captcha-response header
  • โœ… Open API โžก๏ธ OpenApiService
  • โœ… SCIM โžก๏ธ ScimService from ngx-better-auth/scim

๐Ÿ”„ Real-time Session

AuthService keeps the session in sync automatically

  • session โ†’ a signal with the current session or null
  • isLoggedIn โ†’ a computed boolean

Demonstration of usage in a component

import { AuthService } from "ngx-better-auth"
import { inject } from "@angular/core"

@Component({
    // ...
})
export class MyComponent {
    private readonly authService = inject(AuthService)

    get isLoggedIn() {
        return this.authService.isLoggedIn()
    }

    get userName() {
        return this.authService.session()?.user.name
    }
}

โšก Signal resources for reads

GET/list-style methods keep their existing Observable API and also expose Angular resource factories for zoneless-friendly templates.

Mutations such as sign in, sign out, update, delete, revoke, invite, and verify still use Observable because they are command workflows.

Available resource factories

  • SessionService.sessionsResource()
  • AccountService.accountsResource()
  • PasskeyService.userPasskeysResource()
  • OrganizationService.organizationsResource()
  • OrganizationService.fullOrganizationResource(() => params)
  • OrganizationService.invitationResource(() => params)
  • OrganizationService.invitationsResource(() => params)
  • OrganizationService.userInvitationsResource()
  • OrganizationService.activeMemberResource()
  • AdminService.usersResource(() => params)
  • AdminService.userSessionsResource(() => params)
  • StripeService.listResource(() => params)
  • MultiSessionService.deviceSessionsResource()
  • OrganizationService.activeMemberRoleResource(() => params)
  • OrganizationService.rolesResource(() => params)
  • OrganizationService.teamsResource(() => params)
  • OrganizationService.userTeamsResource()
  • OrganizationService.teamMembersResource(() => params)

Simple read resource

import { Component, inject } from '@angular/core'
import { SessionService } from 'ngx-better-auth'

@Component({
    // ...
})
export class SessionsComponent {
    private readonly sessionService = inject(SessionService)

    readonly sessions = this.sessionService.sessionsResource()
}
@if (sessions.isLoading()) {
    <p>Loading sessions...</p>
} @else if (sessions.error()) {
    <p>Unable to load sessions.</p>
} @else {
    @for (session of sessions.value() ?? []; track session.token) {
        <p>{{ session.ipAddress }}</p>
    }
}

Parameterized read resource

import { Component, inject, signal } from '@angular/core'
import { OrganizationService } from 'ngx-better-auth'

@Component({
    // ...
})
export class OrganizationComponent {
    private readonly organizationService = inject(OrganizationService)

    readonly organizationId = signal<string | undefined>(undefined)

    readonly organization = this.organizationService.fullOrganizationResource(() => ({
        organizationId: this.organizationId(),
        membersLimit: 20,
    }))

    selectOrganization(organizationId: string) {
        this.organizationId.set(organizationId)
    }
}

Call resource.reload() after a mutation when you need to refresh a read resource manually.

๐Ÿ›ก๏ธ Guards

This library ships with guards to quickly set up route protection.

Helpers

  • redirectUnauthorizedTo(['/login']) โ†’ redirect if not logged in
  • redirectLoggedInTo(['/']) โ†’ redirect if already logged in
  • hasRole(['admin'], ['/unauthorized']) โ†’ restrict access by role and redirect if not authorized
  • hasOrganizationRole(['owner', 'admin'], ['/unauthorized']) โ†’ restrict access by active organization role
  • hasActiveOrganization(['/select-organization']) โ†’ require an active organization member

Usage in routes

import { Routes } from '@angular/router'
import { canActivate, redirectLoggedInTo, redirectUnauthorizedTo, hasRole, hasOrganizationRole, hasActiveOrganization } from 'ngx-better-auth'

export const routes: Routes = [
  {
    path: '',
    component: SomeComponent,
    ...canActivate(redirectUnauthorizedTo(['/login']))
  },
  {
    path: 'admin',
    component: AdminComponent,
    ...canActivate(hasRole(['admin'], ['/unauthorized']))
  },
  {
    path: 'organization',
    component: OrganizationComponent,
    ...canActivate(hasOrganizationRole(['owner', 'admin'], ['/unauthorized']))
  },
  {
    path: 'organization/select',
    component: SelectOrganizationComponent,
    ...canActivate(hasActiveOrganization(['/select-organization']))
  },
  {
    path: 'login',
    component: LoginComponent,
    ...canActivate(redirectLoggedInTo(['/']))
  }
]

โœ… Validators

The username plugin provides validators that work seamlessly with both reactive and template-driven forms.

import { FormControl } from '@angular/forms'
import { inject } from '@angular/core'
import { UsernameAvailableValidator } from 'ngx-better-auth'

const usernameService = inject(UsernameService)
const initialUsername = 'thomas-orgeval'

const usernameControl = new FormControl('', {
    asyncValidators: [usernameAvailableValidator(usernameService, initialUsername)],
    updateOn: 'change'
})

๐Ÿ’ฐ Stripe subscriptions

Configure Better Auth with stripeClient({ subscription: true }), then inject StripeService to start checkouts, list subscriptions, cancel, restore, or open the billing portal.

import { inject } from '@angular/core'
import { StripeService } from 'ngx-better-auth/stripe'

export class UsageComponent {
    private readonly subscriptions = inject(StripeService)

    startCheckout(orgId: string) {
        return this.subscriptions.upgrade({
            plan: 'starter',
            annual: false,
            customerType: 'organization',
            referenceId: orgId,
            successUrl: '/organization/usage?checkout=success',
            cancelUrl: '/organization/usage?checkout=cancelled',
        })
    }
}

โญ Star History

Star History Chart