Google Sign-in with Angular and Node.js

kellyblaire

Kelly Okere

Posted on October 15, 2024

Google Sign-in with Angular and Node.js

Implementing Google Sign-In in an Angular application allows users to authenticate using their Google accounts, providing a seamless and secure login experience. Below is a comprehensive, step-by-step guide to integrating Google Sign-In using the latest Google Identity Services (GIS) library.

Table of Contents

  1. Prerequisites
  2. Create a Google Cloud Project and Configure OAuth Consent
  3. Install Required Dependencies
  4. Add Google Identity Services Script
  5. Create a Google Sign-In Component
  6. Handle Authentication
  7. Protect Routes (Optional)
  8. Logout Functionality
  9. Security Considerations
  10. Conclusion

Prerequisites

Before you begin, ensure you have the following:

  • Angular CLI installed. If not, install it using:
  npm install -g @angular/cli
Enter fullscreen mode Exit fullscreen mode
  • Node.js and npm installed on your machine.
  • A Google account to access Google Cloud Console.

1. Create a Google Cloud Project and Configure OAuth Consent

  1. Create a New Project:
  • Go to the Google Cloud Console.
  • Click on the project dropdown and select New Project.
  • Enter a Project Name and click Create.
  1. Configure OAuth Consent Screen:
  • Navigate to APIs & Services > OAuth consent screen.
  • Choose External for the user type and click Create.
  • Fill in the App name, User support email, and other required fields.
  • Add Scopes as needed (for basic sign-in, default scopes are sufficient).
  • Add Test Users if you're using an external user type.
  • Save and continue through the steps until completion.
  1. Create OAuth 2.0 Client ID:
  • Go to APIs & Services > Credentials.
  • Click Create Credentials > OAuth client ID.
  • Select Web application as the application type.
  • Enter a Name (e.g., "Angular App").
  • In Authorized JavaScript origins, add your development and production URLs, e.g., http://localhost:4200 and https://yourdomain.com.
  • In Authorized redirect URIs, add the redirect URI if needed (for GIS, it might not be necessary).
  • Click Create and note down the Client ID.

2. Install Required Dependencies

For this implementation, we'll use the Google Identity Services (GIS) library directly without additional Angular-specific packages.

Optionally, you can install @types/google.accounts for TypeScript support.

npm install --save @types/google.accounts
Enter fullscreen mode Exit fullscreen mode

Note: If @types/google.accounts is not available, you can declare the types manually.

3. Add Google Identity Services Script

You need to include the GIS library in your Angular application. The recommended way is to add the script in the index.html.

  1. Open src/index.html.

  2. Add the following script tag inside the <head> section:

   <script src="https://accounts.google.com/gsi/client" async defer></script>
Enter fullscreen mode Exit fullscreen mode
   <!DOCTYPE html>
   <html lang="en">
     <head>
       <meta charset="utf-8" />
       <title>YourApp</title>
       <base href="/" />

       <meta name="viewport" content="width=device-width, initial-scale=1" />
       <link rel="icon" type="image/x-icon" href="favicon.ico" />
       <script src="https://accounts.google.com/gsi/client" async defer></script>
     </head>
     <body>
       <app-root></app-root>
     </body>
   </html>
Enter fullscreen mode Exit fullscreen mode

4. Create a Google Sign-In Component

Create a dedicated component for handling Google Sign-In.

  1. Generate a new component:
   ng generate component google-sign-in
Enter fullscreen mode Exit fullscreen mode
  1. Implement the Component:

Open src/app/google-sign-in/google-sign-in.component.ts and update it as follows:

   import { Component, OnInit, NgZone } from '@angular/core';

   declare const google: any;

   @Component({
     selector: 'app-google-sign-in',
     templateUrl: './google-sign-in.component.html',
     styleUrls: ['./google-sign-in.component.css']
   })
   export class GoogleSignInComponent implements OnInit {

     constructor(private ngZone: NgZone) { }

     ngOnInit(): void {
       this.initializeGoogleSignIn();
     }

     initializeGoogleSignIn() {
       google.accounts.id.initialize({
         client_id: 'YOUR_GOOGLE_CLIENT_ID',
         callback: (response: any) => this.handleCredentialResponse(response)
       });

       google.accounts.id.renderButton(
         document.getElementById('google-signin-button'),
         { theme: 'outline', size: 'large' }  // customization attributes
       );

       google.accounts.id.prompt(); // also display the One Tap dialog
     }

     handleCredentialResponse(response: any) {
       // response.credential is the JWT token
       console.log('Encoded JWT ID token: ' + response.credential);

       // You can decode the JWT token here or send it to your backend for verification
       // For demonstration, we'll just log it

       // If using NgZone, ensure any UI updates are run inside Angular's zone
       this.ngZone.run(() => {
         // Update your application state here, e.g., store user info, navigate, etc.
       });
     }

   }
Enter fullscreen mode Exit fullscreen mode

Important:

  • Replace 'YOUR_GOOGLE_CLIENT_ID' with the Client ID obtained from the Google Cloud Console.
  • handleCredentialResponse will receive a credential (JWT) that you can verify on your backend.
  1. Update the Component Template:

Open src/app/google-sign-in/google-sign-in.component.html and add a container for the Google Sign-In button:

   <div id="google-signin-button"></div>
Enter fullscreen mode Exit fullscreen mode

You can style or position this div as needed.

  1. Add the Component to Your App:

For example, include it in app.component.html:

   <app-google-sign-in></app-google-sign-in>
Enter fullscreen mode Exit fullscreen mode

5. Handle Authentication

After the user signs in, you'll receive a JWT (JSON Web Token) in the handleCredentialResponse callback. You need to:

  1. Decode the JWT (Optional Client-Side):

For security reasons, it's recommended to verify the token on the server. However, if you need to decode it client-side:

   npm install jwt-decode
Enter fullscreen mode Exit fullscreen mode
   import jwt_decode from 'jwt-decode';

   handleCredentialResponse(response: any) {
     const token = response.credential;
     const decoded: any = jwt_decode(token);
     console.log(decoded);

     // Extract user information
     const user = {
       name: decoded.name,
       email: decoded.email,
       picture: decoded.picture,
       // ... other fields
     };

     // Handle user data as needed
   }
Enter fullscreen mode Exit fullscreen mode
  1. Verify the Token on the Backend:

It's crucial to send the token to your backend server for verification to ensure its validity and to prevent security issues.

Example (assuming you have a backend API endpoint):

   import { HttpClient } from '@angular/common/http';

   constructor(private ngZone: NgZone, private http: HttpClient) { }

   handleCredentialResponse(response: any) {
     const token = response.credential;

     // Send the token to your backend for verification
     this.http.post('https://your-backend.com/api/auth/google', { token })
       .subscribe({
         next: (res) => {
           // Handle successful authentication
         },
         error: (err) => {
           // Handle errors
         }
       });
   }
Enter fullscreen mode Exit fullscreen mode

Backend Verification:

On your server, use Google's libraries to verify the token's integrity. For example, in Node.js:

   const { OAuth2Client } = require('google-auth-library');
   const client = new OAuth2Client(CLIENT_ID);

   async function verify(token) {
     const ticket = await client.verifyIdToken({
       idToken: token,
       audience: CLIENT_ID,
     });
     const payload = ticket.getPayload();
     const userid = payload['sub'];
     // If request specified a G Suite domain:
     // const domain = payload['hd'];
     return payload;
   }
Enter fullscreen mode Exit fullscreen mode

6. Protect Routes (Optional)

To protect certain routes in your Angular application and ensure that only authenticated users can access them, you can implement route guards.

  1. Create an Auth Service:

Generate a service to manage authentication state.

   ng generate service auth
Enter fullscreen mode Exit fullscreen mode
   // src/app/auth.service.ts
   import { Injectable } from '@angular/core';
   import { BehaviorSubject } from 'rxjs';

   @Injectable({
     providedIn: 'root'
   })
   export class AuthService {
     private authState = new BehaviorSubject<boolean>(false);
     authState$ = this.authState.asObservable();

     constructor() { }

     setAuthState(state: boolean) {
       this.authState.next(state);
     }

     isAuthenticated(): boolean {
       return this.authState.value;
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Update Auth Service on Sign-In:

In your GoogleSignInComponent, inject and use the AuthService:

   import { AuthService } from '../auth.service';

   constructor(private ngZone: NgZone, private authService: AuthService) { }

   handleCredentialResponse(response: any) {
     // After successful verification with backend
     this.ngZone.run(() => {
       this.authService.setAuthState(true);
       // Navigate to a protected route, e.g.,
       // this.router.navigate(['/dashboard']);
     });
   }
Enter fullscreen mode Exit fullscreen mode
  1. Create an Auth Guard:
   ng generate guard auth
Enter fullscreen mode Exit fullscreen mode
   // src/app/auth.guard.ts
   import { Injectable } from '@angular/core';
   import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
   import { Observable } from 'rxjs';
   import { AuthService } from './auth.service';

   @Injectable({
     providedIn: 'root'
   })
   export class AuthGuard implements CanActivate {

     constructor(private authService: AuthService, private router: Router) { }

     canActivate(
       route: ActivatedRouteSnapshot,
       state: RouterStateSnapshot
     ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
       if (this.authService.isAuthenticated()) {
         return true;
       } else {
         this.router.navigate(['/login']);
         return false;
       }
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Protect Routes:

In your routing module, apply the guard to routes that require authentication.

   // src/app/app-routing.module.ts
   import { NgModule } from '@angular/core';
   import { RouterModule, Routes } from '@angular/router';
   import { DashboardComponent } from './dashboard/dashboard.component';
   import { LoginComponent } from './login/login.component';
   import { AuthGuard } from './auth.guard';

   const routes: Routes = [
     { path: 'login', component: LoginComponent },
     { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
     { path: '', redirectTo: '/login', pathMatch: 'full' },
     // ... other routes
   ];

   @NgModule({
     imports: [RouterModule.forRoot(routes)],
     exports: [RouterModule]
   })
   export class AppRoutingModule { }
Enter fullscreen mode Exit fullscreen mode

7. Logout Functionality

To allow users to log out, you need to clear their authentication state and optionally revoke the token.

  1. Add a Logout Method in Auth Service:
   // src/app/auth.service.ts
   logout() {
     this.authState.next(false);
     // Optionally, revoke the token
     google.accounts.id.disableAutoSelect();
     // Remove tokens from storage if stored
   }
Enter fullscreen mode Exit fullscreen mode
  1. Create a Logout Button:

In your component (e.g., DashboardComponent), add a logout button.

   <button (click)="logout()">Logout</button>
Enter fullscreen mode Exit fullscreen mode
   // src/app/dashboard/dashboard.component.ts
   import { Component } from '@angular/core';
   import { AuthService } from '../auth.service';
   import { Router } from '@angular/router';

   @Component({
     selector: 'app-dashboard',
     templateUrl: './dashboard.component.html',
     styleUrls: ['./dashboard.component.css']
   })
   export class DashboardComponent {

     constructor(private authService: AuthService, private router: Router) { }

     logout() {
       this.authService.logout();
       this.router.navigate(['/login']);
     }
   }
Enter fullscreen mode Exit fullscreen mode

8. Security Considerations

  • Token Verification: Always verify the ID token on your backend server to ensure its integrity and to prevent malicious logins.

  • HTTPS: Ensure your application is served over HTTPS, especially in production, as OAuth 2.0 requires secure contexts.

  • Scopes: Request only the necessary scopes needed for your application to minimize privacy concerns.

  • Token Storage: Avoid storing tokens in localStorage or sessionStorage to prevent XSS attacks. Consider using HttpOnly cookies for storing tokens if possible.

9. Conclusion

Integrating Google Sign-In into your Angular application enhances user experience by providing a quick and secure authentication method. By following the steps outlined above, you can implement Google Sign-In using the latest Google Identity Services library, ensuring your application adheres to current best practices and security standards.

Remember to handle tokens securely and verify them on your backend to maintain the integrity and security of your authentication flow. Additionally, always keep your dependencies updated and monitor Google's documentation for any changes or updates to their authentication services.

If you encounter any issues or need further customization, refer to the Google Identity Services documentation for more detailed information and advanced configurations.

💖 💪 🙅 🚩
kellyblaire
Kelly Okere

Posted on October 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related