Skip to main content
The UserService provides comprehensive authentication methods using Supabase Auth, including password-based auth, OAuth, magic links, and user management.

Import

import { UserService } from '@jet/services/user/user.service';
import { User } from '@supabase/supabase-js';

Usage

Access Current User

import { inject, Signal } from '@angular/core';
import { UserService } from '@jet/services/user/user.service';
import { User } from '@supabase/supabase-js';

export class ProfileComponent {
  readonly #userService = inject(UserService);
  
  readonly user: Signal<User | null> = this.#userService.user;

  ngOnInit() {
    const currentUser = this.user();
    if (currentUser) {
      console.log('User ID:', currentUser.id);
      console.log('Email:', currentUser.email);
    }
  }
}

Sign Up with Email/Password

async signUp(email: string, password: string) {
  try {
    const { data, error } = await this.#userService.signUp(email, password);
    
    if (error) {
      console.error('Sign up failed:', error.message);
      return;
    }
    
    console.log('User created:', data.user);
    console.log('Check email for confirmation');
  } catch (error) {
    console.error('Sign up error:', error);
  }
}

Sign In with Email/Password

async signIn(email: string, password: string) {
  try {
    const { data, error } = await this.#userService.signInWithPassword(email, password);
    
    if (error) {
      this.alertService.showErrorAlert('Invalid credentials');
      return;
    }
    
    console.log('Signed in:', data.user);
    this.router.navigate(['/dashboard']);
  } catch (error) {
    this.alertService.showErrorAlert('Sign in failed');
  }
}

Sign In with OAuth

async signInWithGoogle() {
  try {
    const { data, error } = await this.#userService.signInWithOauth('google');
    
    if (error) {
      console.error('OAuth failed:', error.message);
      return;
    }
    
    // Redirect to OAuth provider
    if (data.url) {
      window.location.href = data.url;
    }
  } catch (error) {
    console.error('OAuth error:', error);
  }
}
async sendMagicLink(email: string) {
  try {
    const { error } = await this.#userService.signInWithOtp(email);
    
    if (error) {
      this.alertService.showErrorAlert('Failed to send magic link');
      return;
    }
    
    this.alertService.showAlert('Check your email for the magic link');
  } catch (error) {
    console.error('Magic link error:', error);
  }
}

Sign Out

async signOut() {
  try {
    const { error } = await this.#userService.signOut();
    
    if (error) {
      console.error('Sign out failed:', error.message);
      return;
    }
    
    this.router.navigate(['/sign-in']);
  } catch (error) {
    console.error('Sign out error:', error);
  }
}

Reset Password

async resetPassword(email: string) {
  try {
    const { error } = await this.#userService.resetPasswordForEmail(email);
    
    if (error) {
      this.alertService.showErrorAlert('Failed to send reset link');
      return;
    }
    
    this.alertService.showAlert('Check your email for password reset link');
  } catch (error) {
    console.error('Reset password error:', error);
  }
}

Update User

async updateEmail(newEmail: string) {
  try {
    const { data, error } = await this.#userService.updateUser({
      email: newEmail
    });
    
    if (error) {
      this.alertService.showErrorAlert('Failed to update email');
      return;
    }
    
    this.alertService.showAlert('Email updated successfully');
  } catch (error) {
    console.error('Update user error:', error);
  }
}

Properties

user

Read-only signal containing the current authenticated user.
type
Signal<User | null>
Reactive signal that updates when auth state changes
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:42
public get user(): Signal<null | User> {
  return this.#user.asReadonly();
}
Example:
// Access in component
const currentUser = this.userService.user();

// Use in template
<div *ngIf="userService.user() as user">
  <p>Welcome, {{ user.email }}</p>
</div>

// Use in computed signals
const isAuthenticated = computed(() => this.userService.user() !== null);

// Use in effects
effect(() => {
  const user = this.userService.user();
  if (user) {
    console.log('User changed:', user.id);
  }
});

Methods

signUp

Creates a new user account with email and password.
email
string
required
User’s email address
password
string
required
User’s password
return
Promise<AuthResponse>
Promise resolving to auth response with user data and session
data
{ user: User | null; session: Session | null }
User and session data if successful
error
AuthError | null
Error object if sign up failed
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:84
public signUp(email: string, password: string): Promise<AuthResponse> {
  return this.#supabaseClient.auth.signUp({
    email,
    options: { emailRedirectTo: this.#getRedirectUrlWithReturnUrl() },
    password,
  });
}
Example:
const { data, error } = await this.userService.signUp(
  '[email protected]',
  'securePassword123'
);

if (data.user) {
  console.log('User created:', data.user.id);
  console.log('Confirmation email sent');
}

signInWithPassword

Authenticates a user with email and password.
email
string
required
User’s email address
password
string
required
User’s password
return
Promise<AuthTokenResponsePassword>
Promise resolving to auth response with user and session
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:76
public signInWithPassword(email: string, password: string): Promise<AuthTokenResponsePassword> {
  return this.#supabaseClient.auth.signInWithPassword({ email, password });
}
Example:
const { data, error } = await this.userService.signInWithPassword(
  '[email protected]',
  'password123'
);

if (data.session) {
  console.log('Signed in:', data.user);
  console.log('Access token:', data.session.access_token);
}

signInWithOauth

Initiates OAuth authentication flow with a provider.
oauthProvider
OauthProvider
required
OAuth provider to use (currently only ‘google’ is supported)
return
Promise<OAuthResponse>
Promise resolving to OAuth response with provider URL
data
{ url: string | null; provider: string }
OAuth provider URL to redirect to
error
AuthError | null
Error object if OAuth initialization failed
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:62
public signInWithOauth(oauthProvider: OauthProvider): Promise<OAuthResponse> {
  return this.#supabaseClient.auth.signInWithOAuth({
    options: { redirectTo: this.#getRedirectUrlWithReturnUrl(), skipBrowserRedirect: true },
    provider: oauthProvider,
  });
}
Example:
const { data, error } = await this.userService.signInWithOauth('google');

if (data.url) {
  // Redirect user to Google OAuth
  window.location.href = data.url;
}

signInWithOtp

Sends a magic link (one-time password) to the user’s email.
email
string
required
User’s email address to send magic link to
return
Promise<AuthOtpResponse>
Promise resolving to OTP response
data
object
Success response data
error
AuthError | null
Error object if sending OTP failed
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:69
public signInWithOtp(email: string): Promise<AuthOtpResponse> {
  return this.#supabaseClient.auth.signInWithOtp({
    email,
    options: { emailRedirectTo: this.#getRedirectUrlWithReturnUrl(), shouldCreateUser: false },
  });
}
Example:
const { error } = await this.userService.signInWithOtp('[email protected]');

if (!error) {
  console.log('Magic link sent to email');
}
The shouldCreateUser: false option means magic links only work for existing users.

signOut

Signs out the current user and clears the session.
return
Promise<{ error: AuthError | null }>
Promise resolving to sign out result
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:80
public signOut(): Promise<{ error: AuthError | null }> {
  return this.#supabaseClient.auth.signOut();
}
Example:
const { error } = await this.userService.signOut();

if (!error) {
  this.router.navigate(['/sign-in']);
  this.storageService.clearLocalStorage();
}

resetPasswordForEmail

Sends a password reset email to the user.
email
string
required
Email address to send reset link to
return
Promise<PasswordResetResponse>
Promise resolving to password reset response
data
object | null
Success response data
error
AuthError | null
Error object if sending reset email failed
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:54
public resetPasswordForEmail(email: string): Promise<...> {
  return this.#supabaseClient.auth.resetPasswordForEmail(email, {
    redirectTo: this.#getRedirectUrlWithReturnUrl('/update-password'),
  });
}
Example:
const { error } = await this.userService.resetPasswordForEmail('[email protected]');

if (!error) {
  console.log('Password reset email sent');
}

updateUser

Updates the current user’s attributes.
userAttributes
UserAttributes
required
Object containing attributes to update
userAttributes.email
string
New email address
userAttributes.password
string
New password
userAttributes.data
object
Custom user metadata
return
Promise<UserResponse>
Promise resolving to user response with updated user data
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:92
public updateUser(userAttributes: UserAttributes): Promise<UserResponse> {
  return this.#supabaseClient.auth.updateUser(userAttributes);
}
Example:
// Update email
const { data } = await this.userService.updateUser({
  email: '[email protected]'
});

// Update password
const { data } = await this.userService.updateUser({
  password: 'newSecurePassword123'
});

// Update custom metadata
const { data } = await this.userService.updateUser({
  data: { preferences: { theme: 'dark' } }
});

getClaims

Retrieves the current user’s JWT claims.
return
Promise<ClaimsResponse>
Promise resolving to JWT claims data
data
{ claims: JwtPayload; header: JwtHeader; signature: Uint8Array } | null
Decoded JWT token data
error
AuthError | null
Error object if retrieval failed
Source: /home/daytona/workspace/source/src/app/services/user/user.service.ts:46
public getClaims(): Promise<...> {
  return this.#supabaseClient.auth.getClaims();
}
Example:
const { data, error } = await this.userService.getClaims();

if (data) {
  console.log('User ID:', data.claims.sub);
  console.log('Email:', data.claims.email);
  console.log('Token expires:', data.claims.exp);
}

Type Definitions

OauthProvider Type

import { Provider } from '@supabase/supabase-js';

export type OauthProvider = Extract<Provider, 'google'>;
Source: /home/daytona/workspace/source/src/app/types/oauth-provider.type.ts:3

Features

Reactive User State

The user signal automatically updates when auth state changes:
this.#supabaseClient.auth.onAuthStateChange(
  (_authChangeEvent: AuthChangeEvent, authSession: AuthSession | null): void => {
    this.#user.set(authSession?.user ?? null);
  },
);

Automatic Redirects

All auth methods include redirect URLs with optional return paths:
// Reads from query params
const returnUrl = this.#activatedRoute.snapshot.queryParamMap.get(QueryParam.ReturnUrl);

// Builds redirect URL
const redirectUrl = new URL('/sign-in', window.location.origin);
if (returnUrl) {
  redirectUrl.searchParams.set(QueryParam.ReturnUrl, returnUrl);
}

Email Confirmation

Sign up automatically sends confirmation emails:
this.#supabaseClient.auth.signUp({
  email,
  options: { emailRedirectTo: this.#getRedirectUrlWithReturnUrl() },
  password,
});

Configuration

Supabase Setup

The service requires Supabase client injection:
import { SUPABASE_CLIENT } from '@jet/injection-tokens/supabase-client.injection-token';
import { createClient } from '@supabase/supabase-js';
import { environment } from './environments/environment';

const supabaseClient = createClient(
  environment.supabaseUrl,
  environment.supabaseAnonKey
);

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: SUPABASE_CLIENT, useValue: supabaseClient },
    UserService // Must be explicitly provided
  ]
};

Best Practices

  1. Handle errors properly - Always check for errors in auth responses
  2. Use reactive state - Access user through the signal for automatic updates
  3. Redirect after auth - Navigate users after successful authentication
  4. Clear storage on sign out - Remove sensitive data when users log out
  5. Validate inputs - Check email format and password strength before calling auth methods
  6. Show loading states - Display spinners during async auth operations

Common Patterns

Auth Guard

export const authGuard: CanActivateFn = (route, state) => {
  const userService = inject(UserService);
  const router = inject(Router);
  
  const user = userService.user();
  
  if (!user) {
    router.navigate(['/sign-in'], {
      queryParams: { returnUrl: state.url }
    });
    return false;
  }
  
  return true;
};

Sign In Component

export class SignInComponent {
  readonly #userService = inject(UserService);
  readonly #router = inject(Router);
  readonly #alertService = inject(AlertService);
  
  signInForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', Validators.required)
  });

  async onSubmit() {
    if (!this.signInForm.valid) return;

    const { email, password } = this.signInForm.value;
    const { error } = await this.#userService.signInWithPassword(email!, password!);
    
    if (error) {
      this.#alertService.showErrorAlert('Invalid email or password');
      return;
    }
    
    this.#router.navigate(['/dashboard']);
  }

  async signInWithGoogle() {
    const { data, error } = await this.#userService.signInWithOauth('google');
    
    if (error || !data.url) {
      this.#alertService.showErrorAlert('Failed to sign in with Google');
      return;
    }
    
    window.location.href = data.url;
  }
}

Dependencies

The UserService depends on:
  • SUPABASE_CLIENT - Supabase client instance
  • LoggerService - For service initialization logging
  • ActivatedRoute - For reading query parameters
This service is not provided at the root level. You must provide it in your component or module providers.

Build docs developers (and LLMs) love