close icon
Angular

Create Secure Angular Apps in the Cloud with StackBlitz and Auth0

Learn how to create Angular applications secured by Auth0 using StackBlitz, an online cloud IDE for Angular and React web applications powered by Visual Studio Code.

August 07, 2018

Auth0 is proud to be a sponsor of the StackBlitz platform. StackBlitz is an online IDE for web applications that is powered by Visual Studio Code. The platform feels as quick and flexible as its desktop counterpart. It offers us developers with great features such as:

  • Intellisense, Project Search (Cmd/Ctrl+P), Go to Definition, and other key Visual Studio Code features.

  • Hot reloading as we type.

  • Importing NPM packages into a project.

  • Editing while offline thanks to a revolutionary in-browser dev server.

  • A hosted app URL where we see (or share) our live application at any time.

  • And many other great features.

In this blog post, we are going to learn how easy and convenient is to build the foundation of an Angular app in the cloud using StackBlitz and to secure it with Auth0.

Getting Familiar with StackBlitz

Getting started with StackBlitz is super easy. Let's point our browser to https://stackblitz.com. Once there, notice the Start a New Project section that offers us different options to get started.

At the moment of this writing, the options are Angular, React, and Ionic. Just below that area, StackBlitz offers a Featured Projects section that includes two quickstarts from Auth0: Angular and React. Let's go ahead and click on the Auth0 Angular one.

StackBlitz home page

In a few seconds, StackBlitz scaffolds a brand new project environment for us complete with an online code editor and a browser preview, all within the same browser window!

StackBlitz online editor main view

Some cool StackBlitz features to mention here:

  • StackBlitz lets us save our progress so that we can leave the browser and resume our work later on. To save our work, we can sign up with GitHub to create a StackBlitz profile.

  • Our profile showcases all of the work that we have saved in the platform, which effectively allows us to use StackBlitz as an online portfolio that we can share with others. We can think of it as a live code resume!

  • We can also run our GitHub projects in StackBlitz and continue their development in the online editor. StackBlitz currently supports projects using @angular/cli and create-react-app. Support for Ionic, Vue, and custom webpack configs is coming soon!

    • Run the project GitHub repo by providing the username + repo name: stackblitz.com/github/{GH_USERNAME}/{REPO_NAME}

    • Optionally, specify a branch, tag, or commit: .../github/{GH_USERNAME}/{REPO_NAME}/tree/{TAG|BRANCH|COMMIT}

  • We can also provide our project with a custom name that would be reflected in its URL. To do this, locate the project name in the top-left corner, click on the pencil icon, and provide it a new unique name.

Naming the Angular cloud project in StackBlitz editor

I am naming mine angular-cloud. If we take a look at my browser preview domain, we can see that now I also have a custom domain:

StackBlitz live project custom domain

We are going to refer to this URL as the <STACKBLITZ_URL> throughout this tutorial. It is the root URL of our StackBlitz application.

This is very similar to what GitHub does with Github pages! I can share that link with anyone I want to check out my app online. The biggest and most useful difference is that the code is alive on StackBlitz! Other people can interact with my code instead of it being static. They can also fork my project to make it their own and make any changes to it. That is definitely one of the best features of StackBlitz: living code.

"The best feature of StackBlitz is that it acts as a repository of living code. Share, fork, and change your code right within the browser!"

Tweet

Tweet This

We can hide the preview browser by clicking on the top-right corner Close button. But something better to do is to click on Open in New Window. If we do that, StackBlitz opens our app preview into a separate browser tab. We can detach that from our browser and position side to side with the editor window. Just like that, we have recreated the usual editor and browser setup. When we make any changes in the code editor, the preview tab is live and reflects the new changes super fast.

Notice on the editor tab that we have a full working project directory. StackBlitz scaffolds the foundation of our project.

StackBlitz online code text editor view

As a first step, let's jump into the browser preview and learn how to get started with Auth0.

Authentication in Five Steps

In the browser preview, we have a page that lists all the needed steps to enable authentication through Auth0 in this Angular application that lives in StackBlitz.

Auth0 is a global leader in Identity-as-a-Service (IDaaS). It provides thousands of customers with a Universal Identity Platform for their web, mobile, IoT, and internal applications. Our extensible platform seamlessly authenticates and secures more than 2.5B logins per month, making it loved by developers and trusted by global enterprises.

The best part of the Auth0 platform is how streamlined is to get started. Let's follow the five steps listed in the page and discuss them in detail.

StackBlitz live browser preview at custom domain

Step 1: Signing Up and Creating an Auth0 Application

Sign up for a free Auth0 account here

or click on the sign up button in the page. You can start for free and save time with Auth0. A free account offers us:

During the sign-up process, we are going to create something called a Tenant, which represents the product or service to which we are adding authentication. More on this in a moment.

Once we are signed in, we are welcomed into the Auth0 Dashboard. In the left sidebar menu, let's click on "Applications". Let's understand better what this area represents.

Let's say that we have a photo-sharing app called Auth0gram. We then would create an Auth0 tenant called auth0gram. From a customer perspective, Auth0gram is that customer's product or service. Auth0gram is available as a web app that can be accessed through desktop and mobile browsers and as a native mobile app for iOS and Android. That is, Auth0gram is available on 3 platforms: web as a single-page application, Android as a native mobile app, and iOS also as a native mobile app. If each platform needs authentication, then we would need to create 3 Auth0 applications that would connect with each respective platform to provide the product all the wiring and procedures needed to authenticate users through that platform. Auth0gram users would belong to the Auth0 tenant and are shared across Auth0 applications. If we have another product called "Auth0shop" that needs authentication, we would need to create another tenant, auth0shop, and create new Auth0 applications for it depending on the platforms where it lives.

With this knowledge, in "Applications", click on the button "Create Application". A modal titled "Create Application" will open up. We have the option to provide a Name for the application and choose its type.

I'll name this app the same as my StackBlitz one, angular-cloud. Your StackBlitz app will be named something different, feel free to use any names you like. Next, let's choose Single Page Web Applications as the type:

Auth0 Create Application view

Let's click "Create". We are going to be welcomed by a view that asks us "What technology are you using for your web app?". This is a tool that we've created at Auth0 to provide you different quickstarts to get you up and running fast in setting up Auth0 within a project. Feel free to choose Angular and check out the content of that quickstart, but for this app, I am giving you the quick steps that relate to setting up Auth0 specifically for the StackBlitz architecture; therefore, let's continue here.

In the next step, we are going to discuss how to help Angular and Auth0 communicate.

Step 2: Creating a Communication Bridge Between Angular and Auth0

One of the offerings of Auth0 to reduce the overhead of adding and managing authentication is our Universal Login page. Auth0's Universal Login is the most secure way to easily authenticate users for your applications.

How does Universal Login work

Auth0 shows the login page whenever something (or someone) triggers an authentication request. Users will see the login page provided by Auth0. Once they log in, they will be redirected back to our application. With security in mind, for this to happen, we have to specify in the Auth0 Settings to what URLs Auth0 can redirect users once they are authenticated.

Let's click on the Settings tab.

Auth0 application settings tab

Once there, let's scroll down until we see "Allowed Callback URLs". We are going to specify here where we want Auth0 to redirect our users after they are authenticated: <STACKBLITZ_URL>/callback.

In my case, I am going to paste the following URL:

https://angular-cloud.stackblitz.io/callback

As before, use the value of your StackBlitz app URL. Why do we need to append /callback to the root domain? As we are going to see later, we'd want to redirect users to a special Angular component (a view) that processes and saves authentication data in memory and sets a flag in localStorage indicating that the user is logged in.

We also need to tell Auth0 where to redirect a user when they log out. We are going to use the root URL of our application as that target route. Therefore, let's paste the <STACKBLITZ_URL> in the "Allowed Logout URLs" field.

In my case, I am going to paste https://angular-cloud.stackblitz.io there.

Note: If you copy the <STACKBLITZ_URL> from the browser it may come with a forward slash at the end (/). You may remove it after pasting it in the field.

After the user authenticates or logs out, Auth0 will only call back to any of the URLs listed in these fields.

Finally, we need to enable Cross-Origin Authentication. What is Cross-Origin Authentication? When authentication requests are made from our application to Auth0, the user's credentials are sent to a domain which differs from the one that serves our application. Collecting user credentials in an application served from one origin and then sending them to another origin can present certain security vulnerabilities, including the possibility of a phishing attack.

Auth0 provides a cross-origin authentication flow which makes use of third-party cookies. The use of third-party cookies allows Auth0 to perform the necessary checks to allow for secure authentication transactions across different origins. This helps to prevent phishing when creating a single sign-on experience with the Lock widget or a custom login form in our application and it also helps to create a secure login experience even if single sign-on is not the goal.

To allow transactions between our Angular app origin and Auth0, we need to add our root URL, <STACKBLITZ_URL>, to the Allowed Web Origins field in the "Settings".

In my case, I am adding https://angular-cloud.stackblitz.io/ as its value.

Save these settings by scrolling down and clicking on Save Changes or pressing CMD/CTRL + S.

Step 3: Adding Auth0 Configuration Variables to Angular

From the Auth0 Application, we need configuration variables to allow our Angular application to use the communication bridge we previously created. Within the Auth0 Application Settings page, we need the following variables:

  • Domain

When we created our new account with Auth0, we were asked to pick a name for our Tenant. This name, appended with auth0.com, will be our Auth0 Domain. It's the base URL we will be using to access the Auth0 API and the URL where users are redirected in order to authenticate.

Custom domains can also be used to allow Auth0 to do the authentication heavy lifting for us without compromising on branding experience.

  • Client ID

Each application is assigned a Client ID upon creation. This is an alphanumeric string and it's the unique identifier for our application (such as q8fij2iug0CmgPLfTfG1tZGdTQyGaTUA). It cannot be modified and we will be using it in our application's code when we call Auth0 APIs.

Warning: Another important piece of information present in the "Settings" is the Client Secret. It protects your resources by only granting tokens to requestors if they're authorized. Think of it as your application's password which must be kept confidential at all times. If anyone gains access to your Client Secret they can impersonate your application and access protected resources.

In the next section, we will discuss the Angular project structure present on StackBlitz. For now, let's start wiring our Angular app with Auth0. Open the environment.ts file present in the src/environments/ folder. We need to replace the property values of the auth object with values from the "Settings" as follows:

// src/environments/environment.ts

export const environment = {
  production: false,
  auth: {
    CLIENT_ID: "[YOUR_CLIENT_ID]",
    CLIENT_DOMAIN: "[YOUR_AUTH0_DOMAIN]", // e.g., 'you.auth0.com'
    REDIRECT: "[OUR-AUTH0-CALLBACK]",
    LOGOUT_URL: "[YOUR-AUTH0-LOGOUT-URL]"
  }
};
  • CLIENT_ID value with the "Client ID" value.
  • CLIENT_DOMAIN value with "Domain" value.
  • REDIRECT value with "Allowed Callback URLs" value.
  • LOGOUT_URL value with "Allowed Logout URLs" value.

We'll use these variables to let our application identify itself as an authorized party to interact with the Auth0 authentication server.

Step 4: Log in

Press the login button to test that we are communicating correctly with Auth0 and that we can get authenticated.

If everything was set up correctly, we are going to be redirected to the Universal Login page.

Auth0 Universal Login page

As explained earlier, this login page is provided by Auth0 with batteries included. It powers not only the login but also the signup of new users into our application. If you have any existing user already, go ahead and log in; otherwise, sign up as a new user.

Alternatively, you may also sign up and log in with Google.

An advantage of the Universal Login page is that it is part of the Auth0 domain, not StackBlitz or our Angular app. It lets us delegate the process of user authentication, including registration, to Auth0 which makes it both convenient and secure.

Unless you signed up with Google, if you created a new user through the sign-up process, you will receive an email asking you to verify your email. There are tons of settings that we can tweak to customize the signup and login experience of our users, such as requiring a username for registration. Feel free to check out the different options presented to you by Auth0 within the Dashboard and the Auth0 documentation.

Once we signed up or logged in, we are taken back to our Angular app hosted at StackBlitz. Notice that the button in the jumbotron (the giant header at the top of the page) changed from Login to Logout, which means that we are authenticated.

Angular app showing logged-in authenticated session

Step 5: Accessing Guarded Routes

Our application uses Angular to guard the /account route. Angular route guards are for the UI only. They don't confer any security when it comes to accessing an API. However, if we were to enforce authentication and authorization in our API (as we should do in all our apps), we could take advantage of guards to authenticate and redirect users as well as stop unauthorized navigation.

The /account route guard prevents navigation to it if the user is not authenticated. Since we have logged in, when we click on the guarded route link that points to /account, we are successfully taken to that view. In case that we were logged out, we would remain on the home screen.

Access to guarded Account View

Authentication Integration Completed

That's it! All that is left is for you to continue building your project in StackBlitz or to export the project locally by downloading it. Feel free to dive deeper into the Auth0 Documentation to learn more about how Auth0 helps you save time on implementing and managing identity. However, in the next sections, we'll explore what is happening under-the-hood of our Angular application in relation to authentication with Auth0.

As a bonus, feel free to click the logout button. You'll be taken to the home page as specified in environment.ts.

Auth0 Angular Starter

This application was created using the Angular CLI; thus, the project structure may feel familiar. Our application code centers around the contents of the src folder.

Within the src folder we find:

  • app directory: It holds all the constructs that belong to the app and build it.
  • environments directory: It holds configuration for different environments such as development and production.
  • index.html: The entry point for the frontend application.
  • main.ts: The entry point for the Angular application.
  • polyfills.ts: This file includes polyfills needed by Angular and is loaded before the app. You can add your own extra polyfills to this file.
  • styles.css: Application-wide (global) styles. Add your own to customize the app's look.

Inside the app folder is where the core Angular development happens. Here we find:

  • app.module.ts, which bootstraps the application using the app.component.ts.
  • app-routing.module.ts, which defines the root routes of the app.
  • We have three folders that define components of the app:
    • home: Holds a component that defines the Home view of our app.
    • callback: The route that points to this component will be called by Auth0 once it completes the authentication process successfully. This component has logic that saves the authentication data returned by Auth0 in memory.
    • account: Holds a component that defines an Account view that presents user profile information. This is a private view that requires authentication.
  • We have an auth folder that holds everything related to the authentication feature of our application, which is powered by Auth0.

This is the gist of the project structure available to us. Next, let's learn about the authentication flow that this Angular application is following.

Authentication Under the Hood

We were able to use authentication successfully and easily just like if it was magic. Let's now open the curtains and see Auth0 in action.

Within the app/auth folder we have two files:

  • auth.service.ts creates a service that handles of all our authentication flow.
  • auth.guard.ts creates a route guard based on authentication that we can use within our router configuration.

Let's explore fully the authentication flow and how the rest of our application interacts with the authentication service and route guard.

Initializing the Authentication Service

When our application is built, AuthService, which lives in auth.service.ts, is initialized. AuthService is a service provided to the whole application by AppModule:

// src/app/app.module.ts

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

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    AccountComponent,
    CallbackComponent
  ],
  imports: [BrowserModule, AppRoutingModule],
  providers: [AuthService],
  bootstrap: [AppComponent]
})
export class AppModule {}

Notice that the only role of the AuthService constructor is to inject the Angular router, Router, in our application:

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  constructor(private router: Router) {}

  // ...
}

When AuthService is instantiated, we also create an instance of auth0.WebAuth that we store in a private variable called _Auth0. What is this?

auth0.WebAuth initializes a new instance of an Auth0 application as follows:

// src/app/auth/auth.service.ts

// ...

export class AuthService {
  // Create Auth0 web auth instance

  private _Auth0 = new auth0.WebAuth({
    clientID: environment.auth.CLIENT_ID,
    domain: environment.auth.CLIENT_DOMAIN,
    responseType: "id_token token",
    redirectUri: environment.auth.REDIRECT,
    scope: "openid profile email"
  });

  // ...
}

auth0.WebAuth is a constructor that takes as argument an object with properties that serve as configuration options to the Auth0 application. The properties that this object requires are domain and clientID which, as we saw earlier, represent the Domain and Client ID variables from the Auth0 Application Settings.

Earlier, we also updated those values in src/environments/environment.ts to match the Settings values.

The other two properties we should define that are optional are responseType, redirectUri, and scope.

responseType can be any space-separated list of the values code, token, and id_token, which are tokens used by Auth0. It defaults to token unless a redirectUri is provided, then it defaults to code. Here, we select both id_token and token.

id_token is a JSON Web Token (JWT) that contains user profile attributes represented in the form of claims. The ID Token is consumed by the application and used to get user information like the user's name, email, and so forth, typically used for UI display.

token is a credential that can be used by an application to access an API. Auth0 uses Access Tokens to protect access to the Auth0 Management API, for example.

We are going to display user profile information in the Account view so we need to request id_token. We also request token as an exercise since we are not going to be making any API request in the scope of this starter app, but it will be there if you decide to do so.

redirectUri represents the URL that we want Auth0 to call when it authenticates our users.

Lastly, scope is a string that indicates what are the default scope(s) used by the application.

OpenID Connect (OIDC) is an authentication protocol that sits on top of OAuth 2.0 Authorization Framework, and allows the application to verify the identity of the users and obtain basic profile information about them in a interoperable way. This information can be returned in the ID Token we specified in the responseType, "id_token token".

The basic and required scope for OpenID Connect is the openid scope. This scope represents the intent of the application to use the OIDC protocol to verify the identity of the user. In OpenID Connect (OIDC), we have the notion of claims. These claims are user attributes and are intended to provide the application with user details such as email, name, and picture.

We defined scope as follows:

 scope: "openid profile email"

We specify the required openid scope. The basic claim returned for the openid scope is the sub claim, which uniquely identifies the user. Applications can ask for additional scopes, separated by spaces, to request more information about the user. We also ask for the profile and email scopes.

The profile scope will request the claims representing basic profile information. These are name, family_name, given_name, middle_name, nickname, picture, and updated_at.

The email scope will request the email and email_verified claims.

When we specify id_token as a responseType, the Auth0 authentication server replies to our request with an object that will contain the idTokenPayload property. idTokenPayload has as properties the claims of the openid, profile, and email scopes that we specified. A decoded id_token looks like this;

{
  "name": "John Doe",
  "nickname": "john.doe",
  "picture": "https://myawesomeavatar.com/avatar.png",
  "updated_at": "2017-03-30T15:13:40.474Z",
  "email": "john.doe@test.com",
  "email_verified": false,
  "iss": "https://YOUR_AUTH0_DOMAIN/",
  "sub": "auth0|USER-ID",
  "aud": "YOUR_CLIENT_ID",
  "exp": 1490922820,
  "iat": 1490886820,
  "nonce": "crypto-value",
  "at_hash": "IoS3ZGppJKUn3Bta_LgE2A"
}

You can read our documentation on OIDC scopes for further details.

We'll use the Auth0 application stored in _Auth0 throughout AuthService. We'll store the idTokenPayload object in an userProfile$ observable. Within the AccountComponent, we are going to use the name, nickname, and picture properties to populate a user profile template.

Logging In

The process of authentication is manually kicked when a user clicks on the login button. This action triggers the login method exposed by AuthService. In turn, login calls the authorize method of the _Auth0 application instance:

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  login = () => this._Auth0.authorize();

  // ...
}

webAuth.authorize() can be used for logging in users via Universal Login, or via social connections. This method invokes the /authorize endpoint of the Authentication API to start an authentication/authorization transaction. It can take a variety of parameters via an options object.

Since we want to invoke the Universal Login page, we only need to call the authorize() method without any additional parameters.

As we learned, with Universal Login, users are taken to a login page hosted by Auth0. Here, users will enter their credentials and log in. If the login is successful, Auth0 will redirect the users to the callback URL we specified. Recall that our callback URL points to our /callback route. According to the router configuration in app-routing.module.ts this route calls the CallbackComponent:

// app-routing.module.ts

// ...

const routes: Routes = [
  {
    path: "",
    component: HomeComponent
  },
  {
    path: "account",
    component: AccountComponent,
    canActivate: [AuthGuard]
  },
  {
    path: "callback",
    component: CallbackComponent
  }
];

// ...

CallbackComponent is a super lean component. Its constructor injects AuthService and it has a method within its ngOnInit lifecycle hook that processes the successful login from Auth0, handleLoginCallback():

// src/app/callback/callback.component.ts

import { Component, OnInit } from "@angular/core";
import { AuthService } from "../auth/auth.service";

@Component({
  selector: "app-callback",
  template: `
    <p>
      Loading...
    </p>
  `
})
export class CallbackComponent implements OnInit {
  constructor(private auth: AuthService) {}

  ngOnInit() {
    this.auth.handleLoginCallback();
  }
}

Let's learn more on how we handle the callback from Auth0.

Handling the Auth0 Callback from Authentication

this.auth.handleLoginCallback() is a method exposed by the public API of AuthService. When Auth0 redirects the user to our /callback route, it includes an authentication response with all the authentication data we requested as a URL hash fragment, which is appended to the /callback route.

We need to extract that data from the URL hash and save it in memory. To do that, we call the webAuth.parseHash method which we are going to manage using an Observable created through the bindNodeCallback method from RxJS. In order to preserve the scope of this, we'll bind() it like so:

bindNodeCallback(this._Auth0.parseHash.bind(this._Auth0));

Here's the complete code:

// src/app/auth/auth.service.ts

// Other imports
import { BehaviorSubject, bindNodeCallback, Observable, of } from "rxjs";

export class AuthService {
  // ...

  // Create observable of Auth0 parseHash method to gather auth results
  parseHash$ = bindNodeCallback(this._Auth0.parseHash.bind(this._Auth0));

  // ...

  handleLoginCallback = () => {
    if (window.location.hash && !this.authenticated) {
      this.parseHash$().subscribe({
        next: authResult => {
          this._setAuth(authResult);

          window.location.hash = "";

          this.router.navigate([this.onAuthSuccessUrl]);
        },
        error: err => this._handleError(err)
      });
    }
  };

  // ...
}

If you need a refresher on Observables, feel free to visit this RxJS Observable Guide from the Angular Team.

The contents of the authResult object returned by this._Auth0.parseHash depend upon which authentication parameters were used in the responseType of the _Auth0 instance configuration. It can include:

  • accessToken: An Access Token for the API.
  • expiresIn: A string containing the expiration time (in seconds) of the accessToken.
  • idToken: An ID Token JWT containing user profile information.
  • idTokenPayload: A payload object that contains the specified scope claims.

Since we requested id_token and token, we get all these properties in the authResult object.

To save this data in memory, we call the auxiliary method, _setAuth:

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  private _setAuth = authResult => {
    // Save authentication data and update login status subject

    // Observable of token
    this.token$ = of(authResult.accessToken);

    // Emit value for user data subject
    this.userProfile$.next(authResult.idTokenPayload);

    // Set flag in local storage stating this app is logged in
    localStorage.setItem(this._authFlag, JSON.stringify(true));
  };

  // ...
}

This method plays a very important role. _setAuth receives as its argument the authResult object. At this point, we are certain that authentication was successful and we store a flag in localStorage to communicate that state change across the application, globally. Other methods will check the value of the this._authFlag flag to determine if the user is authenticated or not. Soon, we'll learn more about how we control this flag.

We store accessToken in a reactive stream, token$. We also store idTokenPayload, which contains all the user profile information, in another reactive stream, userProfile$. Why use RxJS streams here? We want to be able to have tight control of the asynchronous nature of our application. By storing this data that can be used by different elements within our application in streams, we allow these elements to subscribe to the streams and get the most up-to-date value for the data.

The AccountComponent makes use of userProfile$ to populate the user profile information:

// src/app/account/account.component.ts

import { Component, OnInit } from "@angular/core";
import { AuthService } from "../auth/auth.service";

import { Component, OnInit } from "@angular/core";
import { AuthService } from "../auth/auth.service";

@Component({
  selector: "app-account",
  templateUrl: "./account.component.html"
})
export class AccountComponent implements OnInit {
  profile: any;

  constructor(public authService: AuthService) {}

  ngOnInit() {
    this.authService.userProfile$.subscribe(data => {
      if (data) {
        this.profile = { ...data };
      }
    });
  }
}

Initially, the value of data is null; thus, no information is displayed. When userProfile$ pushes the user profile information within this._setAuth, data becomes a valid object and we store its value immutably in this.profile. Thus, the component renders the account information. If we navigate to /account after we log in from the /home view, we won't see this so much in action. If we were to refresh the page while we are in the /account view, we may see a quick delay in the account information showing up if we are authenticated. That happens because we refresh authentication session tokens when we build the application. We'll learn how this happens in detail in a few sections.

After we save the authentication data, we clear the URL hash and navigate to the route stored in onAuthSuccessUrl:

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  // Authentication Navigation
  onAuthSuccessUrl = "/";
  onAuthFailureUrl = "/";
  logoutUrl = environment.auth.LOGOUT_URL;

  // ...

  handleLoginCallback = () => {
    if (window.location.hash && !this.authenticated) {
      this.parseHash$().subscribe({
        next: authResult => {
          this._setAuth(authResult);

          window.location.hash = "";

          this.router.navigate([this.onAuthSuccessUrl]);
        },
        error: err => this._handleError(err)
      });
    }
  };

  // ...
}

At that point, the template of HomeComponent is called, which uses the authenticated() getter method from AuthService to determine the authentication state of the application.

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  get authenticated(): boolean {
    return JSON.parse(localStorage.getItem(this._authFlag));
  }

  // ...
}

This method parses the value of the local storage flag we set earlier. Consumers of this method use its result to handle actions that depend on authentication, such as showing either the Login or Logout label in a button.

AuthGuard, as defined in src/app/auth/auth.guard.ts, is one of the consumers of authenticated. It uses it to determine if it can allow navigation to a route or not:

// src/app/auth/auth.guard.ts

// ...

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    if (this.authService.authenticated) {
      return true;
    } else {
      this.router.navigate([this.authService.onAuthFailureUrl]);
      return false;
    }
  }
}

authenticated is a TypeScript accessor as defined within AuthService at src/app/auth/auth.service.ts:

get authenticated(): boolean {
return JSON.parse(localStorage.getItem(this._authFlag));
}

According to the TypeScript documentation, within classes, TypeScript supports getters and setters to intercept access to a member of an object. This lets developers have tight control over how a member is accessed on each object. We don't have to call it as a function; the logic of the getter or setter is triggered when we try to access the property as usual — for example, using dot notation.

If the user is logged in, authenticated returns true and AuthGuard allows navigation. If it returns false, it not only prevents navigation to the route but also redirects the user to the URL defined with onAuthFailureUrl.

We use AuthGuard to guard the /account route in the routes object of AppRoutingModule:

// src/app/app-routing.module.ts

// ...

const routes: Routes = [
  {
    path: "",
    component: HomeComponent
  },
  {
    path: "account",
    component: AccountComponent,
    canActivate: [AuthGuard]
  },
  {
    path: "callback",
    component: CallbackComponent
  }
];

// ...

If we are authenticated, we can visit /account and see our user profile information displayed.

Checking for an Active Session

What happens if we were to refresh the screen on any view? We have a flag in localStorage that keeps track of whether or not we are logged in. But this flag has no connection with the authentication server at Auth0. Thus, it is ideal for us to have a mechanism that can check if we have an active session with the authentication server if we refresh the page.

We do this by calling this.authService.renewAuth() in the ngOnInit lifecycle hook of the AppComponent. Why there? It's guaranteed that this component will be built whenever we refresh the page, no matter what the active route is.

// src/app/app.component.ts

import { Component, OnInit } from "@angular/core";
import { AuthService } from "./auth/auth.service";

@Component({
  selector: "app-root",
  template: `
    <router-outlet></router-outlet>
    `
})
export class AppComponent implements OnInit {
  constructor(private authService: AuthService) {}

  ngOnInit() {
    this.authService.renewAuth();
  }
}

Let's take a closer look at renewAuth:

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  renewAuth() {
    if (this.authenticated) {
      this.checkSession$({}).subscribe({
        next: authResult => this._setAuth(authResult),
        error: err => {
          localStorage.removeItem(this._authFlag);
          this.router.navigate([this.onAuthFailureUrl]);
        }
      });
    }
  }

  // ...
}

Notice that we only execute the logic in renewAuth if we are logged in. If the local storage flag evaluates to false, the application knows globally that the user is not logged in. If it evaluates to true, we take that value with a grain of salt and verify with the authentication server that we have an active session using webAuth.checkSession. This process also let us acquire new session tokens. We also manage this method with an Observable:

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  // Create observable of Auth0 checkSession method to
  // verify authorization server session and renew tokens
  checkSession$ = bindNodeCallback(this._Auth0.checkSession.bind(this._Auth0));

  // ...
}

The checkSession method allows us to acquire a new token from Auth0 for a user who is already authenticated against Auth0 for our domain. This method accepts any valid OAuth2 parameters that would normally be sent to authorize. If we omit them, it will use the ones we provided when initializing Auth0, the _Auth0 application instance. If the user has a live authentication session with Auth0, we get an authResult object that has the authentication data, similar to what happened within parseHash earlier.

If you used Google or any other social connection, the checkSession call will always return login_required when you are using Auth0 dev keys.

Let's look back at renewAuth:

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  renewAuth() {
    if (this.authenticated) {
      this.checkSession$({}).subscribe({
        next: authResult => this._setAuth(authResult),
        error: err => {
          localStorage.removeItem(this._authFlag);
          this.router.navigate([this.onAuthFailureUrl]);
        }
      });
    }
  }

  // ...
}

On success, checkSession$ calls the next method of our Observer, and we call this._setAuth to save the authentication data in memory. On error, the error method is called. We then remove the logged-in flag from localStorage and redirect the user to the URL defined with this.onAuthFailureUrl.

This refresh logic is run at any time the application is built.

Logging out

Finally, the last step we can take in this authentication workflow is to log out. We do so by calling the logout method of AuthService:

// src/app/auth/auth.service.ts

export class AuthService {
  // ...

  logout = () => {
    // Set authentication status flag in local storage to false
    localStorage.setItem(this._authFlag, JSON.stringify(false));

    // This does a refresh and redirects back to homepage
    // Make sure you have the logout URL in your Auth0
    // Dashboard Application settings in Allowed Logout URLs
    this._Auth0.logout({
      returnTo: this.logoutUrl,
      clientID: environment.auth.CLIENT_ID
    });
  };

  // ...
}

The first step we take within logout is to set the logged-in flag to false. Afterward, we call webAuth.logout through the this._Auth0 instance.

As the name implies, webAuth.logout is used to log out a user. This method accepts an options object, which can include the following optional parameters:

  • returnTo: URL to redirect the user to after the logout action.
  • clientID: Your Auth0 client ID

Note that if the clientID parameter is included, the returnTo URL that is provided must be listed in the Application's Allowed Logout URLs in the Auth0 dashboard. However, if the clientID parameter is not included, the returnTo URL must be listed in the Allowed Logout URLs at the account level in the Auth0 dashboard.

When this._Auth0.logout is called, the logged-in flag becomes false, the Auth0 authentication session is over, and the user is redirected to the specified URL.

Complete Authentication Workflow

This concludes the authentication workflow that is implemented using Angular and Auth0 in this application that is hosted in the cloud using StackBlitz. We went from zero through logging in to logging out and covered some additional procedures to ensure we query the authentication server for an active session when appropriate. The application is ready to be expanded into whatever we want it to become.

Conclusion

I encourage you to learn more about what Auth0 can do to help you meet your identity requirements and goals and to also experiment with developing projects in the cloud using StackBlitz. Our partnership with StackBlitz was carefully selected because we saw the potential it provides to developers around the globe to create highly available applications.

Whether you are building a B2C, B2B, or B2E tool, Auth0 can help you focus on what matters the most to you, the special features of your product. Auth0 can improve your product's security with state-of-the-art features like breached password surveillance and multifactor authentication.

We offer a generous free tier so you can get started with modern authentication.

Let me know how you like Auth0 and StackBlitz in the comments below. Please, feel free to share with us any cool project that you may have live and public on StackBlitz, whatever tech stack it may be!

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon