material-de-estudos

AutorizaĆ§Ć£o e AutenticaĆ§Ć£o no Angular

SumƔrio

AutenticaĆ§Ć£o do usuĆ”rio

Para a autenticaĆ§Ć£o do usuĆ”rio, primeiro Ć© criado um serviƧo de autenticaĆ§Ć£o para centralizar o controle de autenticaĆ§Ć£o do usuĆ”rio em um Ćŗnico serviƧo. Utiliza-se o comando ng generate service common/auth/service/authentication

ng generate service common/auth/service/authentication

UtilizaĆ§Ć£o do Subject e Behavior Subject

Usa-se subjects para armazenar o estado atual do usuĆ”rio e se ele estĆ” logado ou nĆ£o, para permanecer esse estado em torno da aplicaĆ§Ć£o

Subject e BehaviorSubject: Sua funcionalidade Ć© parecida com a de um observable (que Ć© singlecast), sĆ³ que pode ser direcionado a vĆ”rios Observers (multicast)

AlƩm disso usa-se o Session Storage do navegador para armazenar o token de acesso (geralmente JWT) do usuƔrio, e cria-se os mƩtodos de login, logout, getUser e isUserLoggedIn

// app/common/auth/models/login.ts
interface Login {
  email: string;
  password: string;
}
// app/common/auth/service/authentication.service.ts
@Injectable({
  providedIn: "root",
})
export class AuthenticationService extends HttpBaseService {
  private subjectUser: BehaviorSubject<any> = new BehaviorSubject(null);
  private subjectLogin: BehaviorSubject<any> = new BehaviorSubject(false);

  constructor(protected override readonly injector: Injector) {
    super(injector);
  }

  login(login: Login): Observable<any> {
    return this.httpPost<any>("authentication", login).pipe(
      map((response) => {
        sessionStorage.setItem("token", response.token);
        this.subjectUser.next(response.user);
        this.subjectLogin.next(true);

        return response.user;
      })
    );
  }

  logout() {
    sessionStorage.removeItem("token");
    this.subjectUser.next(null);
    this.subjectLogin.next(false);
  }

  isUserLoggedIn(): Observable<any> {
    const token = sessionStorage.getItem("token");
    if (token) {
      this.subjectLogin.next(true);
    }
    return this.subjectLogin.asObservable();
  }

  getUser(): Observable<any> {
    return this.subjectUser.asObservable();
  }
}

Guardas de rotas

Ɖ utilizado para permitir ou negar o acesso em determinadas rotas da aplicaĆ§Ć£o Angular pelo usuĆ”rio. AtravĆ©s de guardas Ć© possĆ­vel usar as seguintes guardas de rotas:

// app/common/auth/guards/auth.guard.ts
@Injectable({
  providedIn: "root",
})
export class AuthGuard implements CanActivate {
  constructor(
    private authenticationService: AuthenticationService,
    private router: Router,
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {

    return this.authenticationService.isUserLoggedIn().pipe(
      tap((isLoggedIn) => {
        if (isLoggedIn == false) {
          this.router.navigateByUrl("/login");

          return false;
        } else {
          return true;
        }
      })
    );
  }
}

@Component({
  selector: "app-login",
  templateUrl: "./login.component.html",
  styleUrls: ["./login.component.scss"],
})
export class LoginComponent implements OnInit {
  constructor(
    private authenticationService: AuthenticationService,
    private router: Router,
    private formBuilder: FormBuilder,
  ) {}

  loginForm!: FormGroup;
  authLogin!: Login;

  ngOnInit(): void {
    this.loginForm = this.formBuilder.group({
      email: ["", [Validators.required, Validators.email]],
      password: [
        "",
        Validators.compose([Validators.required, Validators.minLength(4)]),
      ],
    });
  }

  login() {
    this.authLogin = Object.assign({}, this.authLogin, this.loginForm.value);
    this.authenticationService.login(this.authLogin).subscribe(
      (user) => {
        if (user?.id) {
          this.router.navigateByUrl("dashboard");
        }
      },
    );
  }

  logout() {
    this.authenticationService.logout();
    this.router.navigate(["login"]);
  }
}

ImplementaĆ§Ć£o dos guardas nas rotas

Para implementar nas rotas, basta adicionar o AuthGuard criado nas rotas, usando a propriedade canActivate nas rotas

// app-routing.module.ts
const routes: Routes = [
  { path: "", redirectTo: "dashboard", pathMatch: "full" },
  {
    path: "dashboard",
    loadChildren: () =>
      import("./features/dashboard/dashboard.module").then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
  },
  {
    path: "login",
    loadChildren: () =>
      import("./common/auth/auth.module").then((m) => m.AuthModule),
  },
  { path: "**", component: PageNotFoundComponent },
];
// ...

Implementando Interceptor

Usa-se interceptors para interceptar requisiƧƵes HTTP e modifica-los de forma global (por exemplo adicionando um header) em cada requisiĆ§Ć£o http feita

// app/common/auth/interceptor/auth.interceptor.ts
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const token = sessionStorage.getItem("token");
    let requestClone;

    if (token) {
      requestClone = request.clone({
        setHeaders: {
          // adicionar o token, por exemplo quando se usa JWT
          Authorization: `Bearer ${token}`,
        },
      });
    } else {
      requestClone = request;
    }

    return next.handle(requestClone);
  }
}

Habilitando o Interceptors e o AuthGuard no root

// app.module.ts

@NgModule({
  declarations: [AppComponent, ToolbarComponent, PageNotFoundComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    HttpClientModule,
    //...
  ],
  providers: [
    //...
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
    AuthGuard,
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}