I have some code that goes to my server and returns some data. I have noticed, that i am unable to catch/handle a 403 response.
Code:
canActivate(route: ActivatedRouteRequest, state: RouterStateSnapshot): Observable <boolean> {
return this.myService.getEntitlements()
.pipe(
map(data => {
this.isEntitled = data.hasEntitlements;
if(this.isEntitled === true) {
return true;
}
else {
return false;
}
}),
catchError((err: any) => {
return of(false);
})
};
}
Related
I need help creating a function that gets the categories from my API and also checks for status of the call. I have written my function like the code bellow but it keep showing me the bellow error:
Argument of type '(token: GetResult) => Subscription' is not assignable to parameter of type '(value: GetResult, index: number) => ObservableInput<any>'
Here is the code of my function:
getCategories() {
return from(Preferences.get({ key: "TOKEN_KEY" })).pipe(
switchMap((token) => {
const headers = new HttpHeaders().set(
"Authorization",
`Bearer ${token.value}`
);
return this.httpClient
.get(`${environment.apiUrl}categories`, {
headers,
observe: "response",
})
.subscribe(
(res) => {
return res.body;
},
(error) => {
console.log(error.status);
if (error.status === 400) {
console.log(error.error.message);
}
if (error.status === 401) {
this.authService.logout();
this.router.navigateByUrl("/login", { replaceUrl: true });
}
}
);
})
);
}
You should not use .subscribe inside a subscription. .subscribe returns a Subscription which can't be assigned to the Observable that a switchMap should return.
Use catchError to to handle the error case and a map to handle the success case.
getCategories() {
return from(Preferences.get({ key: 'TOKEN_KEY' })).pipe(
switchMap(token => {
const headers = new HttpHeaders().set('Authorization', `Bearer ${token.value}`);
return this.httpClient.get(
`${environment.apiUrl}categories`,
{ headers, observe: 'response' }
)
}),
catchError(error => {
console.log(error.status);
if (error.status === 400) {
console.log(error.error.message);
}
if (error.status === 401) {
this.authService.logout();
this.router.navigateByUrl('/login', { replaceUrl: true });
}
return EMPTY
}),
map(res => res.body)
)
};
I am trying to call the MSAL silentTokenrefresh method from Angular authInterceptor whenever the 401 hits. And then i am trying to recall the failed request again with a new token so the service won't be interrupted. I have followed this stackoverflow link (answered by Andrei Ostrovski) and implemented the same in my application.
There are two problem with refreshToken() method.
the catchError expects return variable where i put EMPTY. The code is happy, but it is NOT triggering back the failed request where the total purpose is not achieved. It means that whenever it encounters 401 and it is allowing to acquire the new token and then it is not triggering the failed requests.
To resolve the above one, i just took another approach (instead of pipe, i used subscribed but there the return is not applicable) and then it became completely invalid.
Could you please suggest me here to make it work?
My complete authInterceptor file:
addAuthHeader(request) {
const tokenType = "Bearer";
const authHeader = this.sessionService.getAccessToken();
if (authHeader) {
return request.clone({
setHeaders: {
"Authorization": tokenType + " "+ authHeader
}
});
}
return request;
}
refreshToken(): Observable<any> {
if (this.refreshTokenInProgress) {
return new Observable(observer => {
this.tokenRefreshed$.subscribe(() => {
observer.next();
observer.complete();
});
});
} else {
this.refreshTokenInProgress = true;
// Getting the scope.
const loginRequest: { scopes: any } = {
scopes: ['openId','profile'],
};
return this.msalAuthService.acquireTokenSilent(loginRequest).pipe(
tap((payloadInfo)=>{
this.authServices.setSessions(payloadInfo);
this.refreshTokenInProgress = false;
this.tokenRefreshedSource.next();
}),
catchError(() => {
this.refreshTokenInProgress = false;
this.authServices.logout();
return EMPTY;
})
)
}
}
handleResponseError(error, request?, next?) {
// Invalid token error
if (error.status === 401) {
return this.refreshToken().pipe(
switchMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
}),
catchError(e => {
if (e.status !== 401) {
return this.handleResponseError(e);
} else {
this.authentiCationService.logout();
this.router.navigate(["logout"], {
replaceUrl: true
});
}
}));
}
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.totalRequests++;
req = req.clone({ headers: req.headers.set("Accept", "*/*") });
req = req.clone({
headers: req.headers.set("Content-Type", "application/json")
});
req = this.addAuthHeader(req);
const started = Date.now();
return next.handle(req).pipe(
tap(
(event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// some internal logic for the success scenario
}
},
(err: any) => {
this.handleResponseError(err, req, next);
}
)
);
}
The compile time error you are getting is because you are returning the Subscription instead of the Observable. Please take a look at the difference between them: https://angular.io/guide/observables
You have to return the Observable from your code. In catchError return Observable.throw(error.statusText);
I'm trying to implement a role based system on my application and I have the following:
authorize.service.ts
public isAuthenticated(): Observable<boolean> {
return this.getUser().pipe(map(u => !!u));
}
public hasRole(roles: Array<string>): Observable<boolean> {
return this.getUser().pipe(map(u => {
if (!!!u) {
return false;
}
const role = u['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'];
return roles.some(r => r === role);
}));
}
app.routing.ts
...
{ path: 'list', component: ListComponent, canActivate: [AuthorizeGuard], data: { title: 'List', roles: [Role.Admin, Role.Developer, Role.Guest, Role.User]} }
...
And I would like to have the following logic on my AuthorizeGuard:
If isn't authenticated navigate to the login page.
If is logged in and has the required role navigate to the page otherwise navigate to a "forbidden" page.
I have this code on my canActivate:
canActivate(
_next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
this.authorize.isAuthenticated().subscribe(isAuthenticated => {
if (isAuthenticated) {
this.authorize.hasRole(roles).subscribe(hasRole => {
if (hasRole) {
return true;
} else {
this.router.navigate(['forbidden']);
}
});
} else {
this.router.navigate(ApplicationPaths.LoginPathComponents, {
queryParams: {
[QueryParameterNames.ReturnUrl]: state.url
}
});
}
});
return true;
}
But it doesn't work because of the subscribers.
Is there anyway to make wait for the result and return it instead of always return true?
Thanks
You can't mix 2 subscribe and return true in the end, cause it always is true(last return).
You need return Observable<boolean> from the start and chain it to have Observable of false || true at the end
This code is changed in this editor, so it maybe won't work from the start, but you would get the idea of chaining
return this.authorize.isAuthenticated().pipe(
concatMap(isAuth => isAuth ? this.authorize.hasRole(roles) : throwError('NO_AUTH')),
concatMap(hasRole => hasRole ? of(true) : throwError('NO_ROLE')),
catchError(error => {
const isAuthError = error === 'NO_AUTH';
if (isAuthError) {
this.router.navigate(ApplicationPaths.LoginPathComponents, {
queryParams: {
[QueryParameterNames.ReturnUrl]: state.url
}
});
} else {
this.router.navigate(['forbidden']);
}
return of(false);
}),
);
p.s approach with catchError could be reworked to some other
I'm trying to add a new AsyncValidator to check whether user's email already exist in database.
Below is may validator:
export class UniqueEmailValidator implements AsyncValidator {
constructor(private webService: WebWrapperService) {}
validate(ctrl: AbstractControl): Promise < ValidationErrors | null > | Observable < ValidationErrors | null > {
return this.webService.isEmailExistEx(ctrl.value)
.pipe(
map(res => {
console.log("get response" + res);
if (res) {
return { 'uniqueEmail': true};
}
return null;
})
);
}
}
The function isEmailExistEx in service will send a post request to server.
isEmailExistEx(email: string): Observable<boolean> {
this.http.post(this.baseUrl + "auth/verify",
{
"email": email
})
.subscribe(
(val: any) => {
if (!val.result) {
return of(false);
} else {
return of(true);
}
},
response => {
return of(false);
},
() => {
return of(false);
});
}
It reports following error:
A function whose declared type is neither 'void' nor 'any' must return a value.
How should I modify this function?
You're subscribeing to the Observable which will consume the value wrapped in it.
Use map instead of subscribeing and return a boolean value from it::
isEmailExistEx(email: string): Observable<boolean> {
return this.http.post(this.baseUrl + "auth/verify", { email })
.pipe(
map((val: any) => val.result ? true : false)
);
}
i'm working with a angular and i'm trying to apply some AuthGard on some Paths.
The problem is canActivate() renders the content before it checks with the SecurityContext, after a verification that no SecurityContext is applied then a redirection to the default page (login) page is applied.
This is the portion of code responsible for this.
app.routing.ts
{
path: 'admin',
canActivate: [AuthGard],
component: HomeComponent,
children : [
{
path: 'add-merchant-admin',
component : AddMerchantAdminComponent,
},
{
path: 'list-merchant-admin',
component : ListMerchantAdminComponent,
}
]
},
AuthGard.ts
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
this._authService.getRoles().subscribe(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
this.error = false;
}
}
} else {
this._router.navigate(['login']);
this.error = true;
}
}, err => {
this._router.navigate(['login']);
this.error = true;
}
);
return !this.error;
};
AuthService
getRoles() {
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers, withCredentials: true});
return this.http.get('http://10.0.0.239:8080/**/**/RolesResource/getRole', options)
.map((res) => res)
.catch((error: any) => Observable.throw(error.text() || 'Server error'));
}
All Services are correctly injected,
Normally a redirection to protected area or default page should be applied after the verification is made using getRole() method.
The problem you are having is that this._authService.getRoles() makes a network call which is asynchronous. return !this.error; is being fired before the network call is being returned so !this.error does not change and is therefore still truthy.
To solve this issue you should be able to return an observable as follows:
return this._authService.getRoles().map(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
return false;
}
}
} else {
this._router.navigate(['login']);
return true;
}
}).catch((err) => {
this._router.navigate(['login']);
return Observable.of(false);
}
);
Something like this should work
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this._authService.getRoles()
.map(response => JSON.parse(response.text())[0].authority)
.do(role => localStorage.setItem('role', role))
.map( role => role === 'ROLE_ADMIN')
.catch(() => this._router.navigate(['login']));
};
You can try with return observable, which can be updated either true or false.
instead of returning return !this.error; which is always true, try to return
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this._authService.getRoles().map(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
return false;
}
}
} else {
this._router.navigate(['login']);
return true;
}
}, err => {
this._router.navigate(['login']);
return Observable.of(false);
}
);
};
Edited