I am using auth0 to configure my angular 7 app with auth. I followed the quickstart guide to get up and running. The only thing I added in addition to the auth.service.ts file is:
getTokenSilently$(options?): Observable<string> {
return this.auth0Client$.pipe(
concatMap((client: Auth0Client) => from(client.getTokenSilently(options)))
);
}
to support the HTTP interceptor:
import { Injectable } from '#angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '#angular/common/http';
import { AuthService } from './auth.service';
import { Observable, throwError } from 'rxjs';
import { mergeMap, catchError } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {
constructor(private auth: AuthService) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return this.auth.getTokenSilently$().pipe(
mergeMap(token => {
const tokenReq = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
return next.handle(tokenReq);
}),
catchError(err => throwError(err))
)
}
}
The other thing I did differently is that my app does not have a login button, when the app starts up it trys to go to the home page which has an auth guard:
import { Injectable } from '#angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanActivate } from '#angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
import { tap } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean|UrlTree> | boolean {
return this.auth.isAuthenticated$.pipe(
tap(loggedIn => {
if (!loggedIn) {
this.auth.login(state.url);
}
})
);
}
}
so it automatically calls the login so the user gets redirected to auth0. Once they enter their creds it then redirects them back to the home page. Then all of a sudden the auth.service.ts service is loaded again and the auth guard has fired again and it fires the login function again. This seems to just keep on looping.
Any idea why it just keeps on looping?
Replace your Guard method by this one :
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean | UrlTree> | boolean {
return this.auth.isAuthenticated$.pipe(
delay(1000),
tap(loggedIn => {
if (!loggedIn) {
this.auth.login(state.url);
}
})
);
}
The problem was that loggedIn is still at false, even after a successful login.
I am aware that this is not a clean or pretty solution.
I modestly think that the Auth0 Angular sample needs some fixes.
Note: This answer could be helpful to avoid the redirect loop issue for anyone building a Angular SPA
And using the auth0 angular SDK's inbuilt AuthGuard
Or/And using the auth0 provided login page with callback URL
Or facing auth.isAuthenticated$ is always false issue
Try setting the 'Application Type' to 'Single Page Application' in auth0 application properties page
Related
I have create a guard to verify Okta tokens in my guard.However to verify there is another external api that needs to be called.Is it possible and advisable to call that api in the below guard? If yes,how can I go about implementing that?
import { Injectable, CanActivate, ExecutionContext, OnModuleInit } from '#nestjs/common';
import { Observable } from 'rxjs';
import * as OktaJwtVerifier from '#okta/jwt-verifier';
#Injectable()
export class OktaGuard implements CanActivate, OnModuleInit {
oktaJwtVerifier: any;
onModuleInit() {
this.oktaJwtVerifier = new OktaJwtVerifier({
issuer: 'https://{{host}}.okta.com/oauth2/default',
clientId: 'your_client_id'
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const token = context.getArgs()[0].headers.authorization.split(' ')[1];
return this.oktaJwtVerifier.verifyAccessToken(token, 'your_audience')
.then(() => {
return true;
})
.catch((err) => {
console.log(err);
return false;
});
}
}
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.
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'm struggling with connecting the ActivationGuard with an UserAuthenticationService, which is returning from server if the given user is logged or not. The main problem here imo is the fact, it's coming from an asynchro request.
I've got some code, made with help from one kind user on Stack. It's made with observables.
But the problem here is that I'm getting following error, when entering the protected components:
zone.js:388 Unhandled Promise rejection: Cannot read property 'do' of undefined;
My files:
ActivationGuard.ts
import {Injectable} from '#angular/core';
import {Router, CanActivate, RouterStateSnapshot, ActivatedRouteSnapshot} from '#angular/router';
import {Observable} from 'rxjs/Observable';
import {UserAuthenticationService} from './UserAuthenticationService';
#Injectable()
export class ActivationGuard implements CanActivate {
constructor(private router: Router, private userService: UserAuthenticationService) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.userService.isUserAuthenticated
.do(success => {
if (!success) {
this.router.navigate(['/']);
}
});
}
}
UserAuthenticationService.ts
import {Injectable, OnInit} from '#angular/core';
import {Http} from '#angular/http';
import {Observable} from "rxjs/Observable";
#Injectable()
export class UserAuthenticationService implements OnInit {
public isUserAuthenticated: Observable<boolean>;
username: string = 'admin';
constructor(private http: Http) {}
ngOnInit(){
this.isUserAuthenticated = this.http.get(`http://localhost/api/auth/isLogged/${this.username}`)
.map(res => res.json())
.share();
}
}
Looking forward for any kind of help. If you have any questions let me know. Thank you.
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.