Tried to follow AuthGuard example available here:
http://www.sparkbit.pl/angular-2-route-guards-real-life-example/
Unfortunately, while trying to implement the ActivationGuard.ts file, I'm receiving few errors.
ERROR in C:/Users/app/src/app/ActivationGuard.ts (6,24): Cannot find name 'ActivatedRouteSna
pshot'.)
C:/Users/app/src/app/ActivationGuard.ts (6,55): Cannot find name 'RouterStateSnapshot'.)
C:/Users/app/src/app/ActivationGuard.ts (13,62): Cannot find name 'CurrentUserService'.)
C:/Users/app/src/app/ActivationGuard.ts (15,31): Cannot find name 'ActivatedRouteSnapshot'.)
C:/Users/app/src/app/ActivationGuard.ts (15,62): Cannot find name 'RouterStateSnapshot'.)
Which basically means that the elements inside the CanActivate interface and inside constructors are not defined.
routing file:
import { WorksheetAccessGuard } from "./ActivationGuard";
const appRoutes: Routes = [
{ path: '', component: LoginComponent },
{ path: 'app', component: AppComponent, canActivate: [WorksheetAccessGuard] },
{ path: '**', redirectTo: '' }
];
My question: From where could I get these missing elements?
Provided image of my IDE: (the red words are the missing ones)
EDIT
I have made a custom service. I'm not sure if its fine or not:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
#Injectable()
export class UserAuthenticationService {
isUserAuthenticated: boolean = false;
username: string;
constructor(private http: Http) {
}
authentication() {
this.http.get(`http://localhost/api/auth/isLogged/${this.username}`)
.subscribe(res => {
this.isUserAuthenticated = res.json();
},
err => {
console.error('An error occured.' + err);
});
}
}
Now I'm receiving some error inside the AuthGuard file:
ERROR PIC
**My main goal is checking with every component change (when user navigates over the page) if he is logged or not. If not - return him to the login page.
EDIT2
Can I just post all logic from the service in the AuthGuard file? It will look like:
import {Injectable} from '#angular/core';
import {Router, RouterStateSnapshot, ActivatedRouteSnapshot} from '#angular/router';
import {Observable} from 'rxjs/Observable';
import {UserAuthenticationService} from './UserAuthenticationService';
import {Http} from '#angular/http';
interface CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean
}
#Injectable()
export class WorksheetAccessGuard implements CanActivate {
private static username: string;
isUserAuthenticated: boolean = false;
constructor(private router: Router, private userService: UserAuthenticationService, private http: Http) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
this.http.get(`http://localhost/api/auth/isLogged/${this.username}`)
.subscribe(res => {
this.isUserAuthenticated = res.json();
},
err => {
console.error('An error occured.' + err);
});
if (!this.isUserAuthenticated) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
RouterStateSnapshot and ActivatedRouteSnapshot are imported from #angular/router, while the currentUser Service is supposed to be your own where you should store the authenticated state of your User (with a boolean for example).
You retrieve an instance of it through Dependency Injection in your guard's constructor like so :
import { CurrentUserService } from './path/to/your/service/file';
import { RouterStateSnapshot, ActivatedRouteSnapshot } from '#angular/router';
constructor(private userService: CurrentUserService)
{}
Your service needs to be provided in your module, (as well as your guard), and you need to have a property like this in your CurrentUserService :
CurrentUserService :
isAuthenticated: boolean = false;
That way, when you log in from your Login Component (I assume you have one), you can set the service property to true :
LoginComponent :
import { CurrentUserService } from './path/to/your/service/file';
constructor(private userService: CurrentUserService)
{}
login() {
... // Your existing code where you login on form submit or anything
this.userService.isAuthenticated = true;
}
EDIT :
Check out my example, it should fit for yours.
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (!this.authService.isAuthenticated) {
// Deny navigation and redirect to login
this.router.navigate(['/path/to/login']);
return false;
}
// Allow navigation (be careful that the guard always resolve a value)
return true;
}
Related
I`m having problems understanding why I'm not able to set the user in the app. Register and login work.
Upon refreshing the app the user is lost, but the access token is still there. Also when I try to create a product, the manufacturer(owner) of it is undefined and not listed in the DB.
I've been debugging it and I can see that req.user is not found by the app and stays undefined/null.
I`m attaching the whole github project for easier code check, if someone decides to help me out here.
https://github.com/theBoomstick7/projectOils2
Thanks in advance everyone!
Changed all possible setting of the user, register and logout and login do work, but not the intended way.
I understand the design is not good, will be fixed later
As I understand this is not a good way to ask a question here, let me add some parts of the code.
This is the server controller of the product:
const {getAll,createProduct} = require(`../services/productService`)
const productController = require(`express`).Router()
productController.post(`/create`, async(req,res) => {
const data = req.body
console.log(req.user)
try {
const userId = req?.user?._id
const product = await createProduct(data,userId)
res.status(201).json(product)
} catch (error) {
res.status(400).json({error:error.message})
}
res.end()
})
This is the angular auth service :
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { tap } from 'rxjs';
import { IUser } from '../interfaces/user';
const AUTH_API_URL = 'http://localhost:3000' // Change this to environment directory later
#Injectable({
providedIn: 'root'
})
export class AuthService {
user: null | IUser | undefined
constructor(private http: HttpClient, private router: Router) { }
get isLogged(): boolean {
if(this.user){
return true
}else
{
return false
}
}
register(data: {}){
return this.http.post<IUser>(`${AUTH_API_URL}/register`,data).pipe(
tap((user) => {
this.user = user
localStorage.setItem(`accessToken`, this.user.accessToken)
})
)
}
login(data: {}){
return this.http.post<IUser>(`${AUTH_API_URL}/login`, data). pipe(
tap((user) => {
this.user = user
localStorage.setItem(`accessToken`, this.user.accessToken)
})
)
}
logout(){
this.user = null
return localStorage.removeItem(`accessToken`)
}
}
This is the way the user register is handled :
export class RegisterComponent {
errors: any;
constructor(private fb: FormBuilder, private userService: AuthService, private router: Router) {}
registerForm = this.fb.group({
email: [``,[Validators.required, Validators.email]],
username: [``, [Validators.required, Validators.minLength(6)]],
password: [``, [Validators.required, Validators.minLength(6)]],
rePass: [``, [Validators.required,passwordValidator]]
})
register(): void{
this.userService.register(this.registerForm.value).subscribe
({
next: () => this.router.navigate([`/`]),
error:(err)=> {
this.errors = err.error?.error
}
})
this.registerForm.reset()
}
}
This is how a product is created
Product Angular service :
export class ProductsService {
constructor(private http : HttpClient, private router : Router) { }
createProduct(data : {}){
return this.http.post<IProduct>(`${API_URL}/create`, data)
}
}
Create product form :
import { Component } from '#angular/core';
import { FormBuilder, Validators } from '#angular/forms';
import { Route, Router } from '#angular/router';
import { ProductsService } from '../products.service'
#Component({
selector: 'app-create-product',
templateUrl: './create-product.component.html',
styleUrls: ['./create-product.component.css']
})
export class CreateProductComponent {
errors: string | undefined = undefined;
constructor(private fb : FormBuilder, private productService: ProductsService, private router : Router){}
createProductForm = this.fb.group({
title : [``, [Validators.required, Validators.maxLength(12)]],
imageUrl: [``, [ Validators.required]],
description: [``, [Validators.required, Validators.minLength(10)]]
})
createProduct(){
this.productService.createProduct(this.createProductForm.value).subscribe({
next: () => this.router.navigate([`/products`]),
error: (err) => {
this.errors = err.error?.error
}
})
this.createProductForm.reset()
}
}
I hope this makes it easier to everyone.
PP, this is my app interceptor :
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HTTP_INTERCEPTORS } from "#angular/common/http";
import { Injectable, Provider } from "#angular/core";
import { mergeMap, Observable, tap } from "rxjs";
#Injectable()
export class AppInterceptor implements HttpInterceptor{
accessToken:any | [] | null = localStorage.getItem(`accessToken`)
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
{
if(this.accessToken)
{
return next.handle(req.clone({ setHeaders: {'X-Authorization' : this.accessToken}}))
}
else
{
return next.handle(req.clone())
}
}
}
export const AppInterceptorProvider: Provider = {
provide:HTTP_INTERCEPTORS,
useClass: AppInterceptor,
multi:true
}
I hope I understood well your question. If not, let me know. I will edit my answer.
I could debug your App.
You save your accessToken in the local storage, but not the User object. When you refresh the browser, the "accessToken" is there, but the user not because it was removed from the browser's memory.
In this case, your server knows that you are logged in, but your app (frontend) not.
In the auth.service.ts you have to save the user:
localStorage.setItem(`user`, JSON.stringify(this.user));
In the app.component.ts you have to load the user again and set it into the userService instance:
const myUser: string= localStorage.getItem('user') + '';
const loadedUser: IUser = JSON.parse(myUser);
console.log('Loaded User: ' + loadedUser)
this.userService.setLoggedUser(loadedUser);
An alternative solution could be check in the app.component.tsthe presence of the accessToken. If it is there, you can call a new REST method on the server to get the logged user for that token.
Im am working on an Angular 9 application that uses OneLogin for authentication purposes.
In the auth.component.ts file I have an authentication service that I use in the authentication component:
import { AuthService } from 'path/to/core/services/auth/auth.service';
import { AuthApiService } from 'path/to/core/core/services/auth/auth-api.service';
import { Component, OnInit } from '#angular/core';
import { authCodeFlowConfig } from 'path/to/config/onelogin-api/config-auth.component';
#Component({
selector: 'auth',
templateUrl: './assets/auth.component.html',
styleUrls: ['./assets/auth.component.scss']
})
export class AuthComponent implements OnInit{
constructor(private _authService: AuthService) {
}
startAuthentication() {
this._authService.startAuthentication();
}
ngOnInit(): void {
this.startAuthentication();
}
}
In auth.service.ts I have the startAuthentication() method:
startAuthentication(): Observable<any> {
const {issuer, redirectUri, clientId, responseType, scope} = authCodeFlowConfig;
const url = `someURL`;
this.redirectTo(url);
return of(false);
}
redirectTo(url: string): void {
window.location.href = url;
}
In the app.module.ts file I have this array of routes:
import { AuthService } from './core/services/auth/auth.service';
// more imports
const appRoutes: Routes = [
{
path : 'myroute',
redirectTo: 'myroute'
},
{
path: 'auth',
component: AuthComponent
}
];
In other words, I want the application to reach a certain url if login is successful and otherwise redirect to the login form.
What I want to happen is: when login is sucessful - in other words, when startAuthentication() is executed - there should be a redirect to myroute.
I tried {path: 'auth', component: AuthComponent, startAuthentication:[AuthService]} bit it fails.
What am I doing wrong?
As I don't have any further information about your StartAuthentication method, I'd say that you should inject the router service in your component and navigate using it:
import { Router } from '#angular/router';
...
constructor(
private _authService: AuthService,
private _router: Router) {}
startAuthentication() {
this._authService.startAuthentication();
this._router.navigate(['/', 'myroute']);
}
I have two Component and one Service
Components:
1: LoginComponent
2: HeaderComponent (Shared)
Service:
1: authentication.service
In LoginComponent I use authentication.service to get authenticate and after successful authentication, I add User info into the Cookie and in the end, I automatically navigate to return-URL page, in the returned page I have a Component for the header that must show User info from the saved Cookie, BUT there is nothing in the cookie unless I refresh manually the page with F5 button.
My question is how can I access the cookie without refreshing the page?
Update
this is where I want to get the cookie:
import { Component, OnInit } from '#angular/core';
import { CookieHelper } from '../../_helpers/index';
#Component({
moduleId: module.id,
selector: 'app-header-ichart',
templateUrl: 'header.component.html',
styleUrls: ['header.component.css']
})
export class HeaderComponent implements OnInit {
currentUser = '';
isLogged = false;
constructor(private cookie: CookieHelper) { }
ngOnInit() {
this.isLogged = this.cookie.checkCookie('currentUser');
if (this.isLogged) {
this.currentUser = JSON.parse(this.cookie.getCookie('currentUser'));
}
}
}
Update 2
I used your suggestion technic to achieve my goal but need more help:
I Update my AuthenticationService to serve Observable Variable:
AuthenticationService:
import { Injectable } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import { AppConfig } from '../app.config';
import { CookieHelper } from '../_helpers/index';
#Injectable()
export class AuthenticationService {
cookies: Object;
keys: Array<string>;
user$ = new Subject<any>();
constructor(private http: Http, private config: AppConfig, private cookie: CookieHelper) { }
login(username: string, password: string) {
const headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
let body = `grant_type=${'password'}&username=${username}&password=${password}`;
return this.http.post(
this.config.apiUrl + '/token',
body, { headers: headers })
.map((response: Response) => {
// login successful if there's a jwt token in the response
let user = response.json();
if (user && user.access_token) {
user['username'] = username;
// Observable Variable
this.user$.next(JSON.stringify(user));
// store user details and jwt token in cookie to keep user logged in between page refreshes
this.cookie.addCookie('currentUser', JSON.stringify(user));
}
});
}
getUser(): Observable<any> {
return this.user$.asObservable();
}
logout() {
// remove user from cookie to log user out
this.cookie.removeCookie('currentUser');
// Logout Subscribe
this.user$.next(null);
}
}
HeaderComponent:
export class HeaderComponent implements OnInit {
currentUser = '';
isLogged = false;
constructor(private cookie: CookieHelper, private auth: AuthenticationService) { }
ngOnInit() {
// Get the currentUser from Observable Variable
this.auth.getUser().subscribe(currentUser => { this.currentUser = currentUser; });
console.log(this.currentUser);
this.isLogged = this.cookie.checkCookie('currentUser');
if (this.isLogged) {
this.currentUser = JSON.parse(this.cookie.getCookie('currentUser'));
}
}
}
I suggest to use an Observable to achieve that. So your headerComponent will be notified after the login.
Update your authentication.service so that you have something like
import { Subject } from 'rxjs/Subject';
export class AuthenticationService {
user$: Subject<any>;
login() {
// login stuff
this.user$.next('userDetails');
}
logout() {
this.user$.next(null);
}
}
and then from whatever component you want you can check for the user$ observable.
In template with
{{ (user$ | async)?.username }}
or in code with
user$.subscribe(user => console.log(user.username))
I have a web application where there is a JWT token passed to the admin service. This JWT comes from the query URL as there is a redirect from another application. The constructor in the service checks for that URL and set the token value to it (if that params is there).
The problem I am facing is that the canActivateGuard fires too early. When this is called the observable to get the JWT in the service is not solved yet, so the JWT is always not there when the guard is being fired.
I have figure out that to make this work isLoggedIn() in the AdminService has to become an observable that listen to the changes in the URL, and the canActivate() in the guard has to subscribe to it, but can not make this works.
The below code is what I got so far
// Admin Service
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import 'rxjs/Rx';
import {Router, ActivatedRoute, Params} from '#angular/router';
#Injectable()
export class AdminService {
token: string;
constructor(private activatedRoute: ActivatedRoute) {
activatedRoute.queryParams.subscribe(
(params) => {
console.log('queryParams', params);
if(localStorage.getItem('jwt')) {
this.token = localStorage.getItem('jwt');
}
else if(params['jwt']) {
localStorage.setItem('jwt', params['jwt']);
this.token = params['jwt'];
}
});
}
// Check that JWT is in local storage and valid
isLoggedin() {
return (localStorage.getItem('jwt') !== null && localStorage.getItem('jwt') !== 'undefined');
}
}
// Can Activate guard
// Note that this.authService.isLoggedIn() is called before the set JWT in the service is solved
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AdminService,
private router: Router
) {
}
canActivate() {
if (this.authService.isLoggedin()) {
console.log('all ok, proceed navigation to routed component')
return true;
}
else {
// start a new navigation to redirect to login page
this.router.navigate(['/unauthorized']);
return false;
}
}
}
using ActivatedRouteSnapshot and RouterStateSnapshot your problem will be get resolve, and you don't need to subscribe for JWT in your service.
Here is a sample of my code which i had used in my Angular2 application.
auth-guard.ts
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { AuthCookie } from '../shared/services/auth-cookies-handler';
#Injectable()
export default class AuthGuard implements CanActivate {
constructor(private router: Router, private _authCookie: AuthCookie) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
if (this._authCookie.getAuth()) {
return true;
}
else {
this.router.navigate(['/login']);
return false;
}
}
}
Hope this will help you.
While making some edits to my Angular 2 app, I was able to get something working BEFORE I assumed it should be working. In other words, I'm a little perplexed as to why it's working in it's current configuration. Specifically, I have an authentication.service that handles my login auth. And I have both a login component and a chat component, both of which have a private instance of the authentication.service in their respective constructors. What I wanted to do was be able to pass the username from the login functionality down to the chat view, so I could display the logged-in user's username in the chatbox. I've got that working, but, strangely enough, I got it working by adding "this.authenticationService.username = this.model.username;" in the login.component, rather than in the authentication.service. So I'm perplexed as to how/why the chat.component even has access to that info. If I remove that one line of code from my login.component, the username will not be passed through to the chat.component. But I never actually import the login component into the chat component, so how is it working as is? Curious to see if someone can help me understand this. Here are the files in question. First, my authentication.service file:
import { ContextMenu } from './../ui/context-menu.component';
import { Router, RouterLinkActive } from '#angular/router';
import { Injectable } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthenticationService {
username;
constructor(private http: Http) {}
login(username: string, password: string) {
return this.http.post('/api/authenticate', JSON.stringify({ username: username, password: password }))
.map((response: Response) => {
// login successful if there's a jwt token in the response
let user = response.json();
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
}
});
}
isAuthenticated() {
if (localStorage.getItem('currentUser')) {
//console.log('User successfully authenticated...');
return true;
} else {
// console.log('User is not authenticated...');
return false;
}
}
logout() {
// remove user from local storage to log user out
localStorage.removeItem('currentUser');
console.log('User successfully logged out');
}
}
And here's my login.component file:
import { UserService } from './../../data/user.service';
import { AuthenticationService } from './../../data/authentication.service';
import { AlertService } from './../../data/alert.service';
import { Component, OnInit, Input } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-login',
templateUrl: 'app/views/login/login.component.html',
styleUrls: ['app/views/login/login.component.css']
})
export class LoginComponent implements OnInit {
model: any = {};
loading = false;
username;
password;
constructor(
private router: Router,
private authenticationService: AuthenticationService,
private alertService: AlertService,
private userService: UserService) { }
ngOnInit() {
// reset login status
this.authenticationService.logout();
}
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(
data => {
this.router.navigate(['/']);
console.log('User logged in as: ' + this.model.username);
},
error => {
this.alertService.error(error);
this.loading = false;
});
this.authenticationService.username = this.model.username;
}
}
Here's my chat.component file:
import { AuthenticationService } from './../../data/authentication.service';
import { Router, ActivatedRoute } from '#angular/router';
import { ChatService } from './chat.service';
import { Component, OnInit, OnDestroy } from '#angular/core';
import { TabPage } from '../../ui/tab-navigation/tab-page';
#Component({
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.less'],
})
export class ChatComponent extends TabPage implements OnInit, OnDestroy {
messages = [];
users = [];
routes;
connection;
userbase;
route;
message;
user;
constructor(private chatService:ChatService,
router: Router,
route: ActivatedRoute,
private authenticationService: AuthenticationService) {
super(router, route);
this._title = 'Chat Room';
this.addEventListener('paramsChange', function(params) {
this._title = 'Chat Room';
}.bind(this));
}
sendMessage() {
this.chatService.sendMessage(this.message);
this.message = '';
}
sendUser() {
this.chatService.sendUser(this.user);
this.user = '';
}
trackUser() {
this.chatService.trackUser(this.route);
console.log('A user just navigated to ' + this.route);
}
// For when user clicks "enter/return" to send message
eventHandler(event: KeyboardEvent): void {
if (event.key === 'Enter') {
this.chatService.sendMessage(this.message);
this.message = '';
}
}
ngOnInit() {
this.connection = this.chatService.getMessages().subscribe(message => {
this.messages.push(message);
});
this.userbase = this.chatService.getUsers().subscribe(user => {
this.users.push(user);
});
this.routes = this.chatService.getRoutes().subscribe(route => {
this.routes.push(route);
});
}
ngOnDestroy() {
this.connection.unsubscribe();
this.userbase.unsubscribe();
}
public getTabId(params): string {
return 'Chat Room';
}
}
Lastly, my chat.component.html file looks like this (this is where I'm actually using the string interpolation to display the username in the chat):
<div class="centered-display" align="center">
<h3>User: {{authenticationService.username}}</h3>
<div *ngFor="let message of messages" class="message">
{{authenticationService.username}}: {{message.text}}
</div>
<input class="form-group" [(ngModel)]="message" (keypress)="eventHandler($event)">
<div class="spacing">
<button class="submit-btn" md-button (click)="sendMessage()">SEND</button>
</div>
</div>
Can someone help me understand why this is working as is? The one line that makes this work is the last line in my login.component login function: "this.authenticationService.username = this.model.username;". But, again, my chat component doesn't have access to my login component (as as I understand). So why is this working?
Your chat component and login component may not directly relate to each other, but they do both have access to the authenticationService. Specifically, they both have a reference to the same instance of that service injected.
This means that when you do this.authenticationService.username = this.model.username in your login component, you are setting the username property on the same authenticationService object that you are accessing with your interpolation in the chat component view ({{authenticationService.username}}).