I have my application up and running with Angular 2.1.0.
The routes are protected via router Guards, canActivate.
When pointing the browser to a protected area like "localhost:8080/customers" I get redirected to my login page just like expected.
But after a successful login, I would like to be redirected back to calling URL ("/customers" in this case).
The code for handling the login looks like this
login(event, username, password) {
event.preventDefault();
var success = this.loginService.login(username, password);
if (success) {
console.log(this.router);
this.router.navigate(['']);
} else {
console.log("Login failed, display error to user");
}
}
The problem is, I don't know how to get a hold of the calling url from inside the login method.
I did find a question (and answer) regarding this but couldn't really make any sense of it.
Angular2 Redirect After Login
There's a tutorial in the Angular Docs, Milestone 5: Route guards. One possible way to achieve this is by using your AuthGuard to check for your login status and store the url on your AuthService.
AuthGuard
import { Injectable } from '#angular/core';
import {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
}
}
AuthService or your LoginService
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Router } from '#angular/router';
#Injectable()
export class AuthService {
isLoggedIn: boolean = false;
// store the URL so we can redirect after logging in
public redirectUrl: string;
constructor (
private http: Http,
private router: Router
) {}
login(username, password): Observable<boolean> {
const body = {
username,
password
};
return this.http.post('api/login', JSON.stringify(body)).map((res: Response) => {
// do whatever with your response
this.isLoggedIn = true;
if (this.redirectUrl) {
this.router.navigate([this.redirectUrl]);
this.redirectUrl = null;
}
}
}
logout(): void {
this.isLoggedIn = false;
}
}
I think this will give an idea how things work, of course you probably need to adapt to your code
This code will handle your request:
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService,
private router: Router) {
}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> {
return this.authService.isVerified
.take(1)
.map((isVerified: boolean) => {
if (!isVerified) {
this.router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
return false;
// return true;
}
return true;
});
}
}
but be aware that the URL params will not pass with the URL!!
You can find a nice tutorial here :
http://jasonwatmore.com/post/2016/12/08/angular-2-redirect-to-previous-url-after-login-with-auth-guard
The answers I saw were correct.
But the best way to answer your question is returnUrl.
like this:
export class AuthGuardService implements CanActivate {
constructor(private auth: AuthenticationService, private router: Router) { }
canActivate(next: ActivatedRouteSnapshot,
_state: import('#angular/router').RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
let isLoggedIn = false;
const idToken = next && next.queryParamMap.get('id_token');
try {
const expiresAt = idToken && JSON.parse(window.atob(idToken.split('.')[1])).exp * 1000;
if (idToken && expiresAt) {
isLoggedIn = true;
localStorage.setItem('id_token', idToken);
localStorage.setItem('expires_at', String(expiresAt));
} else {
isLoggedIn = this.auth.isLoggedIn();
}
} catch (e) {
console.error(e);
isLoggedIn = this.auth.isLoggedIn();
}
if (!isLoggedIn) {
//this section is important for you:
this.router.navigate(['/login'], { queryParams: { returnUrl: _state.url }});
}
return isLoggedIn;
}
}
This navigate create a url with returnUrl like a param, now you can read returnUrl from param.
GoodLuck.
Related
This Auth guard not working with API response. When I run the code it always returns true. but I want to run guard base on api response. is their way to achieving that?
import {Injectable} from '#angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, Route} from '#angular/router';
import {
HttpClient,
HttpHeaders
} from '#angular/common/http';
import {Observable} from 'rxjs';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private http: HttpClient) {
}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
const sendData = {};
sendData['id'] = sessionStorage.getItem('id');
console.log(JSON.stringify(sendData));
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('token', sessionStorage.getItem('auth') ? sessionStorage.getItem('auth') : 'test');
this.http.post('http://127.0.0.1:5000/auth',
JSON.stringify(sendData), {
headers
})
.subscribe(
(val: any) => {
},
response => {
this.router.navigate(['/login']);
},
() => {
console.log('The POST observable is now completed.');
});
return true;
}
}
Check this. You have returned true at the end of the canActivate authguard. This way it will always return true. Instead you should have it in the subscribe method of the http post request based on your condition. Something like this..
this.http.post('http://127.0.0.1:5000/auth',
JSON.stringify(sendData), {
headers
}).pipe(map(response => {
//suppose we get isAuthenticated bit in response. Your response obj may vary but logic shall be same
if (response.isAuthenticated) {
return true;
}
else {
return false;
}
}));
Thanks.
In the template component AppComponent, depending on the value, the variable this.loggedInService.isLoggedIn switches between the logIn() and logout() methods, which in the application component AppComponent are subscribed to these methods in the service LoggedinServiceand depending on the method, change the value of the variable to true or false.
Also in the Guard's method checkLogin (url: string) I return true or false depending on the value of the variable this.loggedInService.isLoggedIn.
When I start the application, I cannot enter the module, when I click on the button, I can, but when I repeat click on the button "exit", I can still go to the module.
How to make the switch to checkLogin work so that the authentication works correctly and save the value of switching the state between input and output when the page is restarted?
**AppComponent.html: **
<li class="nav-item">
<a class="btn btn-outline-success"
[class.btn-outline-success]="!this.loggedInService.isLoggedIn$"
[class.btn-outline-danger]="this.loggedInService.isLoggedIn$"
(click)="this.loggedInService.isLoggedIn$ ? logout() : logIn()">
{{this.loggedInService.isLoggedIn$ ? 'Exit' : 'Enter'}}
</a>
</li>
**AppComponent.ts **
export class AppComponent implements OnInit {
message: string;
constructor(public loggedInService: LoggedinService,
public router: Router) {
this.setMessage();
}
ngOnInit() {}
logIn(): void {
this.loggedInService.login().subscribe(() => {
if (this.loggedInService.isLoggedIn$) {
let redirect = this.loggedInService.redirectUrl ? this.loggedInService.redirectUrl :
'/gallery';
this.router.navigate([redirect]);
}
});
}
logout(): void {
this.loggedInService.logout();
}
}
LoggedinService:
export class LoggedinService {
isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
isLoggedIn$: Observable<boolean> = this.isLoggedIn.asObservable();
redirectUrl: string;
constructor() {}
login(): Observable < boolean > {
return of(true).pipe(
delay(100),
tap(val => this.isLoggedIn.next(true))
);
}
logout(): void {
this.isLoggedIn.next(false);
}
}
AuthGuard:
export class AuthGuard implements CanActivate {
constructor(
private loggedInService: LoggedinService,
private router: Router
) {}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> {
let url: string = state.url;
return this.loggedInService.isLoggedIn$;
}
checkLogin(url: string): boolean {
if (this.loggedInService.isLoggedIn) {
return true;
} else {
this.loggedInService.redirectUrl = url;
return false;
}
}
}
isLoggedIn in your LoggedinService is a Primitive Data type. So it is not passed by reference. It's passed by value. So if there is a change in it at one place, the same change won't reflect at other places where it is used.
This behavior is only exhibited by Objects as they are passed by reference and NOT value.
You could use a BehaviorSubject to fix this issue.
import { Injectable } from '#angular/core';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { Router } from '#angular/router';
import { delay, tap } from 'rxjs/operators';
#Injectable()
export class LoggedinService {
isLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
isLoggedIn$: Observable<boolean> = this.isLoggedIn.asObservable();
redirectUrl: string;
constructor(private router: Router) { }
login(): Observable<boolean> {
this.isLoggedIn.next(true);
return this.isLoggedIn$;
}
logout(): Observable<boolean> {
this.isLoggedIn.next(false);
return this.isLoggedIn$;
}
}
Now, instead of isLoggedIn of type boolean, you'll get isLoggedIn$ of type Observable which you'll have to subscribe to, to get the logged in status of the user.
You'll have to .subscribe to this.loggedInService.login() and this.loggedInService.login() in your AppComponent as both of them return isLoggedIn$. You'll have to create a local isLoggedIn property and assign it whatever is returned in your .subscribe. You can then set the button text and click handler based on the template based on this isLoggedIn property.
In the case, of AuthGuard, since a guard can return Observable<boolean> or Promise<boolean> or boolean, you can simply return this.loggedInService.isLoggedIn$
Here's a Sample StackBlitz for your ref.
I'm using Angular 4 with ADAL to authenticate users in my web application, using ng2-adal library which is a wrapper for adal.js.
The problem I'm facing is the following:
So the token expires after a time limit and I have a canActivate route guard that checks if the user is authenticated. If not, it navigates the users to the login page. This is how my route guard is looking:
import { Injectable } from '#angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { AdalService } from 'ng2-adal/dist/core';
#Injectable()
export class RouteGuard implements CanActivate {
constructor(private router: Router, private adalService: AdalService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.adalService.userInfo.isAuthenticated) {
return true;
} else {
this.router.navigate(['/user-login']);
return false;
}
}
}
so whenever the token expires, the user is navigated to the login page, which is annoying for the users. Is there a way to renew the token whenever it expires?
I figured it out. This is how I added it:
import { Injectable } from '#angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { AdalService } from 'ng2-adal/dist/core';
#Injectable()
export class RouteGuard implements CanActivate {
constructor(private router: Router, private adalService: AdalService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.adalService.userInfo.isAuthenticated) {
return true;
} else {
this.adalService.acquireToken(this.adalService.config.clientId).toPromise().then((data) => {
console.log('Generating a new authentication token.');
return true;
},
(error) => {
console.log('No user logged in.');
this.router.navigate(['/user-login']);
return false;
}
}
}
}
I had the same issue and my fix worked.
In app.component.ts, add this code to ngOnit().
this.adalService.handleWindowCallback();
this.adalService.acquireToken(this.adalService.config.loginResource).subscribe(token => {
this.adalService.userInfo.token = token;
if (this.adalService.userInfo.authenticated === false) {
this.adalService.userInfo.authenticated = true;
this.adalService.userInfo.error = '';
}
}, error => {
this.adalService.userInfo.authenticated = false;
this.adalService.userInfo.error = error;
this.adalService.login();
});
When token expires, app component gets called, and acquire token refreshes the token silently. But the this.adalService.userInfo.authenticated is still false leading to redirection or again calling login method. So manually setting it to true fixes the redirection error. this.adalService.config.loginResource this is automactically set by adal-angular itself with the resource that we need token for.
Also add expireOffsetSeconds: 320, to adal configuration data settings along with
tenant: configData.adalConfig.tenant,
clientId: configData.adalConfig.clientId,
redirectUri: window.location.origin,
expireoffsetseconds invalidates the token based on the time that we specify before its actual expiry.
I am trying to redirect the users to login page, if the user isn't logged in. Once the user logs, I want to take the user back to the previous page. But I am getting an error as StaticInjectorError[RouterStateSnapshot]: NullInjectorError: No provider for RouterStateSnapshot!.
Here's the code for the CanLoad functionality:
#Injectable()
export class TokenGuardService implements CanLoad {
constructor(private authService: AuthService, private router: Router, private snapshot: RouterStateSnapshot) {}
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean {
if(this.authService.doesTokenExist()) {
return true;
} else {
this.router.navigate(['/login'], { queryParams: { returnUrl: this.snapshot.url }});
return false;
}
}
and here's the code my Component
export class LoginComponent implements OnInit {
constructor(private loginService: LoginService, private router: Router, private route: ActivatedRoute) { }
ngOnInit() {
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/dashboard';
}
onLoginFormSubmit() {
//Some success scenario
if(success) {
this.router.navigate([this.returnUrl]);
}
}
Please help me resolve this issue.
I guess it's quite simple issue, but unfortunately I don't really know how to deal with it.
I'm trying to connect my UserAuthenticationService service with the ActivationGuard.
UserAuthenticationService.ts:
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 => { //^^returns true or false, depending if the user is logged or not
this.isUserAuthenticated = res.json();
},
err => {
console.error('An error occured.' + err);
});
}
}
ActivationGuard.ts
import {Injectable} from '#angular/core';
import {Router, RouterStateSnapshot, ActivatedRouteSnapshot} from '#angular/router';
import {Observable} from 'rxjs/Observable';
import {UserAuthenticationService} from './UserAuthenticationService';
interface CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean
}
#Injectable()
export class WorksheetAccessGuard implements CanActivate {
constructor(private router: Router, private userService: UserAuthenticationService) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.userService) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
Note
It works great, if I just use localStorage to store the information if the user is logged or not:
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (!localStorage.getItem('currentUser')) {
this.router.navigate(['/']);
return false;
}
return true;
}
But how can I connect the service with the guard? Looking forward for any kind of help. Thank you in advance.
If you need any more information, please let me know and I will edit my post.
Call authentication() method of UserAuthenticationService either in constructor or On ngOnit then it sets the isUserAuthenticated variable and use that in the ActivationGuard.ts
UserAuthenticationService.ts:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
#Injectable()
export class UserAuthenticationService {
isUserAuthenticated: boolean = false;
username: string;
constructor(private http: Http) {
this.authentication();
}
authentication() {
this.http.get(`http://localhost/api/auth/isLogged/${this.username}`)
.subscribe(res => { //^^returns true or false, depending if the user is logged or not
this.isUserAuthenticated = res.json();
},
err => {
console.error('An error occured.' + err);
});
}
}
ActivationGuard.ts
#Injectable()
export class WorksheetAccessGuard implements CanActivate {
constructor(private router: Router, private userService: UserAuthenticationService) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.userService.isUserAuthenticated) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
This is not the right approach for doing it. Every time you call the service , it initialize a new instance and hence you get a false.
You should create a singleton service instance ( via the main module in your app) - where it will contain your app state ( in memory / localstorage)
Then , when you'll call UserAuthenticationService - you won't update its owbn parameter but the main's one ( the singleton).
I suggest you to use a BehaviourSubject ( read about it , it's like a Subject but it also yields its last value without waiting to emit a value manually).
From that point your app can see from anywhere ig the user is logged in or not.