Replace the login modal with a separate login page

During my last talk about JHipster I was asked if it is possible to replace the modal login dialog with a dedicated login page. I remember doing that for the angular 1 frontend as it was possible to configure in the router if the content was rendered on a modal dialog or not. As this is not possible anymore with the new AngularX frontend I will describe how to extend a default JHipster application such that the login is at a dedicated route /login.

Default Modal Login

Default Modal Login

Create Login Page Component and Routes

In order to be able to upgrade JHipster continously I strongly advice you not to change genereated code (too much). Therefore the first step is to create a new module instead of modifying the existing LoginComponent under src/main/webapp/shared/login.

To do so create a new folder /src/main/webapp/app/login with following files:

login-page.component.html
The layout of the login page. You can copy the content of the modal-body from src/main/webapp/shared/login/login.component.html
login-page.component.ts
The logic of the new login page, which at the core a modified version of the original login-component.ts
login-page.module.ts
Exposing the new component and route as a separate angular module
login-page.route.ts
Defining the new route.
login-page.route.ts
import { Route } from '@angular/router';

import { LoginPageComponent } from './login-page.component';

export const LOGIN_PAGE_ROUTE: Route = {
  path: 'login',
  component: LoginPageComponent,
  data: {
    authorities: [],
    pageTitle: 'login.title'
  }
};
login-page.component.ts
import { Component, Renderer, ElementRef } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Router } from '@angular/router';
import { JhiEventManager } from 'ng-jhipster';

import { LoginService } from 'app/core/login/login.service';
import { StateStorageService } from 'app/core/auth/state-storage.service';

@Component({
  selector: 'jhi-login-page',
  templateUrl: './login-page.component.html'
})
export class LoginPageComponent {
  authenticationError: boolean;

  loginForm = this.fb.group({
    username: [''],
    password: [''],
    rememberMe: [false]
  });

  constructor(
    private eventManager: JhiEventManager,
    private loginService: LoginService,
    private stateStorageService: StateStorageService,
    private router: Router,
    private fb: FormBuilder
  ) {}

  login() {
    this.loginService
      .login({
        username: this.loginForm.get('username').value,
        password: this.loginForm.get('password').value,
        rememberMe: this.loginForm.get('rememberMe').value
      })
      .subscribe(
        () => {
          this.authenticationError = false;
          if (
            this.router.url === '/account/register' ||
            this.router.url.startsWith('/account/activate') ||
            this.router.url.startsWith('/account/reset/')
          ) {
            this.router.navigate(['']);
          }

          this.eventManager.broadcast({
            name: 'authenticationSuccess',
            content: 'Sending Authentication Success'
          });

          // previousState was set in the authExpiredInterceptor before being redirected to login modal.
          // since login is successful, go to stored previousState and clear previousState
          const redirect = this.stateStorageService.getUrl();
          //TODO Check if the redirect would be login again and route to home
          if (redirect) {
            this.stateStorageService.storeUrl(null);
            this.router.navigateByUrl(redirect);
          } else {
            this.router.navigate(['']);
          }
        },
        () => (this.authenticationError = true)
      );
  }

  register() {
    this.router.navigate(['/account/register']);
  }

  requestResetPassword() {
    this.router.navigate(['/account/reset', 'request']);
  }
}

Prevent opening the modal dialog

The tricky part is how to prevent the modal login dialog to open and instead route the the new /login route. Instead of removing the call to LoginModalService.open() at multiple places I suggest to change the implementation of LoginModalService.open() ([lst-login-modal-service]) as this is very minimal change which should not make much problems when updating JHipster.

You migth need to adapt some calls to LoginModalService.open() as the method now returns nothing anymore. But just let the compiler guide you if in doubt.

login-modal.service.ts
import { Injectable } from '@angular/core';

import { Router } from '@angular/router';

@Injectable({ providedIn: 'root' })
export class LoginModalService {
  constructor(private router: Router) {}

  open() {
    this.router.navigate(['login']);
  }
}

Now the new login page instead of the old modal dialog should open when you click login in the navbar.

Lock down the whole page

As we don’t want to have any public page we need to make sure even the homepage can’t be accessed without being logged in. In order to do so you need to add needed authorities to home.route.ts along with a custom canActivate function.

home.route.ts
import { Route } from '@angular/router';
import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; 3

import { HomeComponent } from './home.component';

export const HOME_ROUTE: Route = {
  path: '',
  component: HomeComponent,
  data: {
    authorities: ['ROLE_USER'], 1
    pageTitle: 'home.title'
  },
  canActivate: [UserRouteAccessService] 2
};
  1. Require user role to access this page
  2. Use the existing UserRouteAccessService to check if the route can be activated
  3. Import the UserRouteAccessService

Now you should be prompted with the new login page when trying to access the application. You might have notices that the menu items in the navbar are still visible even when not logged in. To hide all items unless the user is logged in you can add *jhiHasAnyAuthority="'ROLE_USER'" to the navbar.

Summary

With minimal code change we have removed the usage of the default login modal dialog and replaced it with a simple login page. Furthermore we have changed the application from havin a public part to an application which requires to be logged in.

The source code of the complete project is available on gitlab.

Resulting Login Page

Resulting Login Page