AuthService
Package: @nauth-toolkit/client-angular
Type: Injectable Service
Angular wrapper around NAuthClient that provides promise-based authentication methods and maintains reactive state.
import { AuthService } from '@nauth-toolkit/client-angular';
Overview
AuthService wraps the core NAuthClient and provides:
- Reactive state via BehaviorSubjects (
currentUser$,isAuthenticated$,challenge$) - All NAuthClient methods returning Promises for clean async/await syntax
- Automatic state synchronization on auth state changes
- Direct access to underlying client when needed
AuthService is provided by NAuthModule.forRoot(). It is not providedIn: 'root' — the module registration is required to inject the service.
Properties
Observables
| Property | Type | Description |
|---|---|---|
currentUser$ | Observable<[AuthUser](../api/types/auth-user) | null> | Current authenticated user |
isAuthenticated$ | Observable<boolean> | Authentication state |
challenge$ | Observable<[AuthResponse](../api/types/auth-response) | null> | Current challenge (if any) |
authEvents$ | Observable<[AuthEvent](../../api/types/auth-event)> | All authentication lifecycle events |
authSuccess$ | Observable<[AuthEvent](../../api/types/auth-event)> | Successful authentication events only |
authError$ | Observable<[AuthEvent](../../api/types/auth-event)> | Authentication error events only |
Sync Accessors
| Method | Returns | Description |
|---|---|---|
isAuthenticated() | boolean | Sync auth check (use in guards/templates) |
getCurrentUser() | AuthUser | null | Current user from cache |
getCurrentChallenge() | AuthResponse | null | Current challenge from cache |
Methods
login()
Authenticate with email and password.
async login(identifier: string, password: string): Promise<AuthResponse>
Parameters
| Parameter | Type | Description |
|---|---|---|
identifier | string | User email |
password | string | User password |
Returns
Promise<[AuthResponse](../api/types/auth-response)>- Auth result or challenge
Example
async handleLogin() {
try {
const response = await this.auth.login('user@example.com', 'password');
if (response.challengeName) {
// Handle challenge
this.router.navigate(['/challenge', response.challengeName]);
} else {
// Login successful
this.router.navigate(['/dashboard']);
}
} catch (err) {
this.error = err.message;
}
}
signup()
Register a new user.
async signup(payload: SignupRequest): Promise<AuthResponse>
Returns
Promise<[AuthResponse](../api/types/auth-response)>- Auth result or challenge
Parameters
| Property | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User email |
password | string | Yes | Password |
firstName | string | No | First name |
lastName | string | No | Last name |
phone | string | No | Phone (E.164 format) |
Example
async handleSignup() {
try {
const response = await this.auth.signup({
email: 'new@example.com',
password: 'SecurePass123!',
firstName: 'John',
lastName: 'Doe',
phone: '+14155551234',
});
// Usually returns VERIFY_EMAIL challenge
if (response.challengeName === 'VERIFY_EMAIL') {
this.router.navigate(['/verify-email']);
}
} catch (err) {
this.error = err.message;
}
}
logout()
End current session.
async logout(forgetDevice?: boolean): Promise<void>
Parameters
| Parameter | Type | Description |
|---|---|---|
forgetDevice | boolean | Remove device trust. Default: false |
Example
await this.auth.logout();
// or
await this.auth.logout(true); // Forget device
logoutAll()
End all sessions for current user across all devices.
async logoutAll(forgetDevices?: boolean): Promise<{ revokedCount: number }>
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
forgetDevices | boolean | No | If true, also revokes all trusted devices. Default: false (devices remain trusted). |
Returns
| Property | Type | Description |
|---|---|---|
revokedCount | number | Number of sessions revoked |
This method requires the user to be authenticated. The endpoint is protected and cannot be called publicly.
Examples
// Revoke all sessions but keep devices trusted
const result = await this.auth.logoutAll();
console.log(`Revoked ${result.revokedCount} sessions`);
// Revoke all sessions AND all trusted devices
const result2 = await this.auth.logoutAll(true);
console.log(`Revoked ${result2.revokedCount} sessions and all trusted devices`);
respondToChallenge()
Complete any authentication challenge.
async respondToChallenge(response: ChallengeResponse): Promise<AuthResponse>
Parameters
response-ChallengeResponse- Challenge response (type depends on challenge)
Returns
Promise<[AuthResponse](../api/types/auth-response)>- Next challenge or authentication success
SDK Validation
The SDK performs client-side validation before sending requests. For TOTP setup, it validates that both secret and code are present in setupData.
Example - Email Verification
async verifyEmail(code: string) {
try {
const response = await this.auth.respondToChallenge({
session: this.challengeSession,
type: 'VERIFY_EMAIL',
code,
});
if (response.challengeName) {
// Next challenge
this.handleChallenge(response);
} else {
// Complete
this.router.navigate(['/dashboard']);
}
} catch (err) {
this.error = err.message;
}
}
Example - TOTP Setup (requires both secret and code)
async setupTotp() {
try {
// Get setup data first
const setupResponse = await this.auth.getSetupData(session, 'totp');
const secret = setupResponse.setupData.secret;
// Display QR code to user
this.qrCode = setupResponse.setupData.qrCode;
// When user enters verification code:
const response = await this.auth.respondToChallenge({
session,
type: 'MFA_SETUP_REQUIRED',
method: 'totp',
setupData: {
secret, // Required: from getSetupData
code: this.userEnteredCode, // Required: from user's authenticator app
},
});
if (response.challengeName) {
// Progressive challenge (e.g., email verification next)
this.router.navigate(['/challenge', response.challengeName]);
} else {
// Setup complete, user authenticated
this.router.navigate(['/dashboard']);
}
} catch (err) {
// SDK validation error if secret or code is missing
if (err instanceof NAuthClientError && err.isCode(NAuthErrorCode.VALIDATION_FAILED)) {
console.error('TOTP setup validation failed:', err.message);
}
}
}
resendCode()
Resend verification code.
async resendCode(session: string): Promise<{ destination: string }>
Example
const result = await this.auth.resendCode(this.challengeSession);
console.log('Code sent to:', result.destination);
getSetupData()
Get MFA setup data during MFA_SETUP_REQUIRED challenge.
async getSetupData(session: string, method: string): Promise<GetSetupDataResponse>
Returns
Promise<[GetSetupDataResponse](../api/types/get-setup-data-response)>- Method-specific setup data
Example - TOTP Setup
async initTotpSetup() {
// Get TOTP setup data (QR code, secret, manual entry key)
const response = await this.auth.getSetupData(session, 'totp');
const setupData = response.setupData;
// setupData contains:
// {
// secret: 'JBSWY3DPEHPK3PXP',
// qrCode: 'data:image/png;base64,...',
// manualEntryKey: 'JBSW Y3DP EHPK 3PXP',
// issuer: 'MyApp',
// accountName: 'user@example.com'
// }
// Display QR code
this.qrCode = setupData.qrCode;
this.manualKey = setupData.manualEntryKey;
// Store secret for later use in respondToChallenge
this.secret = setupData.secret;
}
// Later, when user enters verification code:
async completeTotpSetup(userCode: string) {
try {
const response = await this.auth.respondToChallenge({
session,
type: 'MFA_SETUP_REQUIRED',
method: 'totp',
setupData: {
secret: this.secret, // Required: from getSetupData
code: userCode, // Required: from user's authenticator app
},
});
if (response.challengeName) {
// Another challenge required
this.router.navigate(['/challenge', response.challengeName]);
} else {
// Setup complete
this.router.navigate(['/dashboard']);
}
} catch (err) {
// Handle error (e.g., invalid code, missing secret)
console.error('TOTP setup failed:', err);
}
}
getChallengeData()
Get challenge data for MFA verification.
async getChallengeData(session: string, method: string): Promise<GetChallengeDataResponse>
Example
const challengeData = await this.auth.getChallengeData(session, 'passkey');
clearChallenge()
Clear stored challenge session.
async clearChallenge(): Promise<void>
Example
await this.auth.clearChallenge();
getProfile()
Fetch current user profile from server.
async getProfile(): Promise<AuthUser>
Returns
Promise<[AuthUser](../api/types/auth-user)>
Example
const user = await this.auth.getProfile();
console.log('User:', user);
updateProfile()
Update user profile.
async updateProfile(updates: UpdateProfileRequest): Promise<AuthUser>
Parameters
updates-UpdateProfileRequest
Returns
Promise<[AuthUser](../api/types/auth-user)>
Example
const user = await this.auth.updateProfile({
firstName: 'John',
lastName: 'Doe',
});
console.log('Updated:', user);
changePassword()
Change user password.
async changePassword(oldPassword: string, newPassword: string): Promise<void>
Example
await this.auth.changePassword('oldPass123', 'newPass456!');
resetPasswordWithCode()
Reset password with verification code (generic method for both admin and user-initiated resets).
async resetPasswordWithCode(
identifier: string,
code: string,
newPassword: string
): Promise<ResetPasswordWithCodeResponse>
Parameters
| Parameter | Type | Description |
|---|---|---|
identifier | string | User identifier (email, username, phone) |
code | string | Verification code (6-10 digits) |
newPassword | string | New password (min 8 characters) |
Returns
Promise<[ResetPasswordWithCodeResponse](../api/types/reset-password-with-code-response)>
Example
// With code from email
await this.auth.resetPasswordWithCode('user@example.com', '123456', 'NewPass123!');
getMfaStatus()
Get user's MFA status.
async getMfaStatus(): Promise<MFAStatus>
Returns
Promise<[MFAStatus](../api/types/mfa-status)>
Example
const status = await this.auth.getMfaStatus();
console.log('MFA enabled:', status.enabled);
loginWithSocial()
Start redirect-first web social login (performs browser navigation).
async loginWithSocial(provider: SocialProvider, options?: SocialLoginOptions): Promise<void>
Example
await this.auth.loginWithSocial('google', { returnTo: '/auth/callback', appState: '12345' });
exchangeSocialRedirect()
Exchange exchangeToken (from callback URL) into an auth result.
async exchangeSocialRedirect(exchangeToken: string): Promise<AuthResponse>
Example
const response = await this.auth.exchangeSocialRedirect(exchangeToken);
if (response.challengeName) {
// Handle challenge
} else {
// Login successful
}
verifyNativeSocial()
Verify native social token (mobile).
async verifyNativeSocial(request: SocialVerifyRequest): Promise<AuthResponse>
Example
const result = await this.auth.verifyNativeSocial({
provider: 'google',
idToken: nativeIdToken,
});
getLastOauthState()
Get the last OAuth appState from social redirect callback.
Returns the appState that was stored during the most recent social login redirect callback. This is useful for restoring UI state, applying invite codes, or tracking referral information.
The state is automatically stored when the callback guard processes the redirect URL, and is automatically cleared after retrieval to prevent reuse.
async getLastOauthState(): Promise<string | null>
Returns
Promise<string | null>- The stored appState, or null if none exists
Example
// After social login redirect completes (e.g., in dashboard component)
ngOnInit() {
this.checkOauthState();
}
async checkOauthState() {
const appState = await this.auth.getLastOauthState();
if (appState) {
// Apply invite code or restore UI state
console.log('OAuth state:', appState);
// Example: this.applyInviteCode(appState);
}
}
Note
- The appState is also passed as a query parameter to the success route (e.g.,
/dashboard?appState=invite-code-123) - You can read it from the URL query parameters using
ActivatedRouteinstead if preferred - The state is cleared after retrieval, so call this method only once per OAuth flow
See
refresh()
Refresh tokens.
async refresh(): Promise<TokenResponse>
Example
const tokens = await this.auth.refresh();
getClient()
Get underlying NAuthClient instance for advanced operations.
This method is deprecated. Use the direct promise-based methods on AuthService instead. The getClient() method is kept for backward compatibility only and may be removed in a future version.
getClient(): NAuthClient
Example
// Deprecated - use direct methods instead
const client = this.auth.getClient();
await client.getProfile();
// Preferred - use direct methods
await this.auth.getProfile();
Usage Patterns
Reactive Authentication State
@Component({
template: `
@if (auth.isAuthenticated$ | async) {
<app-navbar [user]="auth.currentUser$ | async" />
<router-outlet />
} @else {
<app-login />
}
`,
})
export class AppComponent {
constructor(public auth: AuthService) {}
}
Challenge Flow Navigation
@Component({
/* ... */
})
export class ChallengeRouterComponent implements OnInit {
constructor(
private auth: AuthService,
private router: Router,
) {}
ngOnInit(): void {
this.auth.challenge$.pipe(filter((c) => c !== null)).subscribe((challenge) => {
switch (challenge.challengeName) {
case 'VERIFY_EMAIL':
this.router.navigate(['/verify-email']);
break;
case 'VERIFY_PHONE':
this.router.navigate(['/verify-phone']);
break;
case 'MFA_REQUIRED':
this.router.navigate(['/mfa']);
break;
case 'MFA_SETUP_REQUIRED':
this.router.navigate(['/mfa-setup']);
break;
case 'FORCE_CHANGE_PASSWORD':
this.router.navigate(['/change-password']);
break;
}
});
}
}
Form with Error Handling (Async/Await)
@Component({
template: `
<form [formGroup]="form" (ngSubmit)="submit()">
<input formControlName="email" placeholder="Email" />
<input formControlName="password" type="password" />
@if (error) {
<div class="error">{{ error }}</div>
}
<button [disabled]="loading">
{{ loading ? 'Loading...' : 'Login' }}
</button>
</form>
`,
})
export class LoginComponent {
form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', Validators.required],
});
loading = false;
error = '';
constructor(
private fb: FormBuilder,
private auth: AuthService,
private router: Router,
) {}
async submit() {
if (this.form.invalid) return;
this.loading = true;
this.error = '';
try {
const { email, password } = this.form.value;
const res = await this.auth.login(email!, password!);
if (res.challengeName) {
this.router.navigate(['/challenge']);
} else {
this.router.navigate(['/dashboard']);
}
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
}
}
Related APIs
- NAuthClient - Underlying client class
- NAuthClientConfig - Configuration options
- AuthResponse - Authentication response type
- AuthUser - User profile type
- ChallengeResponse - Challenge response union
- Interceptor - HTTP interceptor for token management
- Guards - Route protection guards
- NAuthClientError - Error handling