CodeNewbie Community 🌱

Cover image for Authentication using the Supabase to an Angular application
Rodrigo Kamada
Rodrigo Kamada

Posted on • Updated on

Authentication using the Supabase to an Angular application

Introduction

Angular is a development platform for building WEB, mobile and desktop applications using HTML, CSS and TypeScript (JavaScript). Currently, Angular is at version 14 and Google is the main maintainer of the project.

Supabase is an authentication service with simple tools to help developers build applications.

Prerequisites

Before you start, you need to install and configure the tools:

Getting started

Create and configure the account on the Supabase

1. Let's create the account. Access the site https://supabase.io/ and click on the button Start your project.

Supabase - Home page

2. Click on the button Continue with GitHub.

Supabase - Sign in

3. Fill in the fields Username or email address, Password and click on the button Sign in to login with your GitHub account and if you don't have an account, click on the button Create an account to create a new account. In this tutorial there is a step-by-step guide to creating a GitHub account.

Supabase - Sign in to GitHub

4. Click on the button Authorize supabase.

Supabase - Authorize supabase

5. Click on the button New project.

Supabase - New project

6. Click on the menu with the organization name created automatically.

Supabase - Choose organization

7. Select an Organization, fill in the fields Name and Database Password, select a Region and click on the button Create new project.

Supabase - Create a new project

8. Wait for the project creation.

Supabase - Setting up the project

9. Click on the button Copy to copy the key that has been generated, in my case, the key eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzNTA4NjA1MCwiZXhwIjoxOTUwNjYyMDUwfQ.CzFuYS6XKvEwW5OsAAPAcHvuo-NVE4PUwDSKgqK9Yas was copied and click on the button Copy to copy the URL that has been generated, in my case, the URL https://wzlpmcsxrxogtctlznel.supabase.co was copied becouse this key and URL will be configured in the Angular application.

Supabase - New project created

10. Click on the menu SQL to setting up the database schema.

Supabase - SQL

11. Click on the card User Management Starter.

Supabase - Scripts

12. Click on the button RUN.

Supabase - User Management Starter

13. Ready! Account created, key generated and database schema configured.

Create the Angular application

1. Let's create the application with the Angular base structure using the @angular/cli with the route file and the SCSS style format.

ng new angular-supabase --routing true --style scss
CREATE angular-supabase/README.md (1062 bytes)
CREATE angular-supabase/.editorconfig (274 bytes)
CREATE angular-supabase/.gitignore (604 bytes)
CREATE angular-supabase/angular.json (3267 bytes)
CREATE angular-supabase/package.json (1080 bytes)
CREATE angular-supabase/tsconfig.json (783 bytes)
CREATE angular-supabase/.browserslistrc (703 bytes)
CREATE angular-supabase/karma.conf.js (1433 bytes)
CREATE angular-supabase/tsconfig.app.json (287 bytes)
CREATE angular-supabase/tsconfig.spec.json (333 bytes)
CREATE angular-supabase/src/favicon.ico (948 bytes)
CREATE angular-supabase/src/index.html (301 bytes)
CREATE angular-supabase/src/main.ts (372 bytes)
CREATE angular-supabase/src/polyfills.ts (2820 bytes)
CREATE angular-supabase/src/styles.scss (80 bytes)
CREATE angular-supabase/src/test.ts (788 bytes)
CREATE angular-supabase/src/assets/.gitkeep (0 bytes)
CREATE angular-supabase/src/environments/environment.prod.ts (51 bytes)
CREATE angular-supabase/src/environments/environment.ts (658 bytes)
CREATE angular-supabase/src/app/app-routing.module.ts (245 bytes)
CREATE angular-supabase/src/app/app.module.ts (393 bytes)
CREATE angular-supabase/src/app/app.component.scss (0 bytes)
CREATE angular-supabase/src/app/app.component.html (24617 bytes)
CREATE angular-supabase/src/app/app.component.spec.ts (1103 bytes)
CREATE angular-supabase/src/app/app.component.ts (221 bytes)
βœ” Packages installed successfully.
    Successfully initialized git.
Enter fullscreen mode Exit fullscreen mode

2. Install and configure the Bootstrap CSS framework. Do steps 2 and 3 of the post Adding the Bootstrap CSS framework to an Angular application.

3. Configure the variable supabase.url with the Supabase URL and the variable supabase.key with the Supabase key in the src/environments/environment.ts and src/environments/environment.prod.ts files as below.

supabase: {
  url: 'https://wzlpmcsxrxogtctlznel.supabase.co',
  key: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzNTA4NjA1MCwiZXhwIjoxOTUwNjYyMDUwfQ.CzFuYS6XKvEwW5OsAAPAcHvuo-NVE4PUwDSKgqK9Yas',
},
Enter fullscreen mode Exit fullscreen mode

4. Install the @supabase/supabase-js library.

npm install @supabase/supabase-js
Enter fullscreen mode Exit fullscreen mode

5. Create the SupabaseService service.

ng generate service supabase --skip-tests=true
CREATE src/app/supabase.service.ts (137 bytes)
Enter fullscreen mode Exit fullscreen mode

6. Change the supabase.service.ts file and add the lines as below.

import { Injectable } from '@angular/core';
import { AuthChangeEvent, createClient, Session, SupabaseClient, User } from '@supabase/supabase-js';

import { environment } from '../environments/environment';

export interface IUser {
  email: string;
  name: string;
  website: string;
  url: string;
}

@Injectable({
  providedIn: 'root',
})
export class SupabaseService {

  private supabaseClient: SupabaseClient;

  constructor() {
    this.supabaseClient = createClient(environment.supabase.url, environment.supabase.key);
  }

  public getUser(): User|null {
    return this.supabaseClient.auth.user();
  }

  public getSession(): Session|null {
    return this.supabaseClient.auth.session();
  }

  public getProfile(): PromiseLike<any> {
    const user = this.getUser();

    return this.supabaseClient.from('profiles')
    .select('username, website, avatar_url')
    .eq('id', user?.id)
    .single();
  }

  public authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void): any {
    return this.supabaseClient.auth.onAuthStateChange(callback);
  }

  public signIn(email: string): Promise<any> {
    return this.supabaseClient.auth.signIn({
      email,
    });
  }

  public signOut(): Promise<any> {
    return this.supabaseClient.auth.signOut();
  }

  public updateProfile(userUpdate: IUser): any {
    const user = this.getUser();

    const update = {
      username: userUpdate.name,
      website: userUpdate.website,
      id: user?.id,
      updated_at: new Date(),
    };

    return this.supabaseClient.from('profiles').upsert(update, {
      returning: 'minimal', // Do not return the value after inserting
    });
  }

}
Enter fullscreen mode Exit fullscreen mode

7. Create the SignInComponent component.

ng generate component sign-in --skip-tests=true
CREATE src/app/sign-in/sign-in.component.scss (0 bytes)
CREATE src/app/sign-in/sign-in.component.html (22 bytes)
CREATE src/app/sign-in/sign-in.component.ts (279 bytes)
UPDATE src/app/app.module.ts (493 bytes)
Enter fullscreen mode Exit fullscreen mode

8. Change the src/app/sign-in/sign-in.component.ts file. Import the Router and SupabaseService services and create the signIn method as below.

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { IUser, SupabaseService } from '../supabase.service';

@Component({
  selector: 'app-sign-in',
  templateUrl: './sign-in.component.html',
  styleUrls: ['./sign-in.component.scss'],
})
export class SignInComponent {

  loading: boolean;
  user: IUser;

  constructor(private router: Router,
              private supabaseService: SupabaseService) {
    this.loading = false;
    this.user = {} as IUser;
  }

  public signIn(): void {
    this.loading = true;
    this.supabaseService.signIn(this.user.email)
    .then(() => {
    }).catch(() => {
      this.loading = false;
    });
  }

}
Enter fullscreen mode Exit fullscreen mode

9. Change the src/app/sign-in/sign-in.component.html file. Add the lines as below.

<div class="row justify-content-center my-5">
  <div class="col-4">
    <div class="card">
      <div class="card-body">
        <div class="row">
          <div class="col mb-2">
            <label for="email" class="form-label">Email:</label>
            <input type="email" id="email" name="email" #email="ngModel" [(ngModel)]="user.email" class="form-control form-control-sm">
          </div>
        </div>
        <div class="row">
          <div class="col d-grid">
            <button type="button" (click)="signIn()" class="btn btn-sm btn-success" [disabled]="loading">
              <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" *ngIf="loading"></span>
              Sign in
            </button>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

10. Create the ProfileComponent component.

ng generate component profile --skip-tests=true
CREATE src/app/profile/profile.component.scss (0 bytes)
CREATE src/app/profile/profile.component.html (22 bytes)
CREATE src/app/profile/profile.component.ts (280 bytes)
UPDATE src/app/app.module.ts (642 bytes)
Enter fullscreen mode Exit fullscreen mode

11. Change the src/app/profile/profile.component.ts file. Import the SupabaseService service and create the update method as below.

import { Component, OnInit } from '@angular/core';
import { IUser, SupabaseService } from '../supabase.service';

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.scss'],
})
export class ProfileComponent implements OnInit {

  loading: boolean;
  user: IUser;

  constructor(private supabaseService: SupabaseService) {
    this.loading = false;
    this.user = {} as IUser;
  }

  public ngOnInit(): void {
    const session = this.supabaseService.getSession();

    if (session && session.user && session.user.email) {
      this.user.email = session.user.email;
    }

    this.supabaseService.getProfile()
    .then((success: any) => {
      if (success && success.profile) {
        this.user.name = success.profile.username;
        this.user.website = success.profile.website;
        this.user.url = success.profile.avatar_url;
      }
    });
  }

  public update(): void {
    this.loading = true;

    this.supabaseService.updateProfile(this.user)
    .then(() => {
      this.loading = false;
    }).catch(() => {
      this.loading = false;
    });
  }

}
Enter fullscreen mode Exit fullscreen mode

12. Change the src/app/profile/profile.component.html file and add the lines as below.

<div class="row justify-content-center my-5">
  <div class="col-4">
    <div class="row" *ngIf="user.url">
      <div class="col mb-2 text-center">
        <img [src]="user.url" class="rounded-circle">
      </div>
    </div>
    <div class="row">
      <div class="col mb-2">
        <label for="email" class="form-label">Email:</label>
        <input type="email" id="email" name="email" #email="ngModel" [(ngModel)]="user.email" disabled class="form-control form-control-sm">
      </div>
    </div>
    <div class="row">
      <div class="col mb-2">
        <label for="name" class="form-label">Name:</label>
        <input type="text" id="name" name="name" #name="ngModel" [(ngModel)]="user.name" class="form-control form-control-sm">
      </div>
    </div>
    <div class="row">
      <div class="col mb-2">
        <label for="website" class="form-label">Website:</label>
        <input type="text" id="website" name="website" #website="ngModel" [(ngModel)]="user.website" class="form-control form-control-sm">
      </div>
    </div>
    <div class="row">
      <div class="col d-grid">
        <button type="button" (click)="update()" class="btn btn-sm btn-dark" [disabled]="loading">
          <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" *ngIf="loading"></span>
          Save
        </button>
      </div>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

13. Change the src/app/app.component.ts file. Import the Router and SupabaseService services and create the isAuthenticated and signOut methods as below.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { SupabaseService } from './supabase.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {

  session: any;

  constructor(private router: Router,
              private supabaseService: SupabaseService) {
    this.session = this.supabaseService.getSession();
  }

  public ngOnInit(): void {
    this.supabaseService.authChanges((_, session) => this.session = session);
  }

  public isAuthenticated(): boolean {
    if (this.session) {
      return true;
    }
    return false;
  }

  public signOut(): void {
    this.supabaseService.signOut()
    .then(() => {
      this.router.navigate(['/signIn']);
    });
  }

}
Enter fullscreen mode Exit fullscreen mode

14. Change the src/app/app.component.html file and add the menu as below.

<nav class="navbar navbar-expand-sm navbar-light bg-light">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Angular Supabase</a>

    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>

    <div id="navbarContent" class="collapse navbar-collapse">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link" routerLink="/signIn" routerLinkActive="active" *ngIf="!isAuthenticated()">Sign in</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="/profile" routerLinkActive="active" *ngIf="isAuthenticated()">Profile</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="" (click)="signOut()" *ngIf="isAuthenticated()">Sign out</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

<router-outlet></router-outlet>
Enter fullscreen mode Exit fullscreen mode

15. Change the src/app/app-routing.module.ts file and add the routes as below.

import { ProfileComponent } from './profile/profile.component';
import { SignInComponent } from './sign-in/sign-in.component';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'signIn',
    pathMatch: 'full',
  },
  {
    path: 'profile',
    component: ProfileComponent,
  },
  {
    path: 'signIn',
    component: SignInComponent,
  },
  {
    path: '**',
    redirectTo: 'signIn',
  },
];
Enter fullscreen mode Exit fullscreen mode

16. Change the src/app/app.module.ts file. Import the FormsModule module and the ProfileComponent and SignInComponent components as below.

import { FormsModule } from '@angular/forms';

import { ProfileComponent } from './profile/profile.component';
import { SignInComponent } from './sign-in/sign-in.component';

declarations: [
  AppComponent,
  ProfileComponent,
  SignInComponent,
],
imports: [
  BrowserModule,
  FormsModule,
  AppRoutingModule,
],
Enter fullscreen mode Exit fullscreen mode

17. Run the application with the command below.

npm start

> angular-supabase@1.0.0 start
> ng serve

βœ” Browser application bundle generation complete.

Initial Chunk Files | Names         |      Size
vendor.js           | vendor        |   2.90 MB
styles.css          | styles        | 268.30 kB
polyfills.js        | polyfills     | 128.51 kB
scripts.js          | scripts       |  76.33 kB
main.js             | main          |  34.20 kB
runtime.js          | runtime       |   6.63 kB

                    | Initial Total |   3.40 MB

Build at: 2021-10-25T02:19:20.036Z - Hash: 2dc1cd0da7856970b0d8 - Time: 17203ms

Warning: /home/rodrigo/Development/Angular/angular-supabase/node_modules/@supabase/realtime-js/dist/module/RealtimeClient.js depends on 'websocket'. CommonJS or AMD dependencies can cause optimization bailouts.
For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies



** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **


βœ” Compiled successfully.
Enter fullscreen mode Exit fullscreen mode

18. Ready! Access the URL http://localhost:4200/ and check if the application is working. See the application working on GitHub Pages and Stackblitz.

Angular Supabase

Testing the application sign in

1. Let's test the application sign in. Access the URL http://localhost:4200/, fill in the field Email and click on the button Sign in.

Application - Sign in

2. Open the email with the subject Confirm Your Signup and click on the link Confirm your mail.

Application - Sign up email

3. At this point, you are already signed in. Click on the menu Profile.

Application - Signed in

4. Fill in the fields Name and Website and click on the button Update.

Application - Profile

5. Go back to the Supabase site and click on the menu New query.

Supabase - SQL

6. Fill in the text field with SELECT * FROM profiles; content and click on the button RUN.

Supabase - New query

7. Check the updated fields username and website.

Supabase - Query result

8. Ready! We test the application sign in and profile update. Supabase documentation is available at https://supabase.io/docs.

The application repository is available at https://github.com/rodrigokamada/angular-supabase.

This tutorial was posted on my blog in portuguese.

Top comments (0)