I always return http 200 back and define my own error code. If using Promise, I could reject a response which contains customized error. How can I do it using Observable? I do it like this and it works, but is it a correct way?
My interceptor:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).flatMap((event: any) => {
if (event instanceof HttpResponse) {
if (event.body.status) {
if (event.body.status === 'NOK') {
return Observable.throw(event.body);
}
}
}
return Observable.create(observer => observer.next(event));
})}
My provider:
private handleErrorObservable(error: Response | any) {
return Observable.throw(error.message || error);
}
constructor(public http: HttpClient) {
}
signIn(params: any) {
...(some more codes here)
return this.http.post(this.apiUrl + func, requestData, options).map(
(response) => {
//do something with the response
return response;
}).catch(this.handleErrorObservable);
}
My component:
if (form.valid) {
this.auth.signIn(this.login).subscribe(val => {
//console.log(val);
this.navCtrl.setRoot('WebPage');
}, err => {
// error handling here
})
}
Related
I am using Jwt tokens for authentication and using a interceptor for adding access token to the requests.I have a getToken() method which is checking for token's validity and calling the service for getting new set of tokens. The method is returning promise but the requests are taking the promise before it gets completed and failing to get the updated token.
Below is my code:
export class TokenService {
refresh = false;
constructor(public injector: Injector) {
}
public getToken(): string | Promise<string> {
const jwtHelper = new JwtHelperService();
let token = localStorage.getItem('token');
let refreshToken = localStorage.getItem('refreshToken');
if (!token || !refreshToken) {
return null;
}
if (jwtHelper.isTokenExpired(token)) {
if (jwtHelper.isTokenExpired(refreshToken)) {
return null;
} else {
let tokenPromise;
if (!this.refresh) {
this.refresh = true;
tokenPromise = this.promiseFromObservable(this.getTokenService(localStorage.getItem('refreshToken')));
}
return tokenPromise;
}
} else {
return token;
}
}
getTokenService(refreshToken: string) {
let http = this.injector.get(HttpClient);
const httpOptions = {
headers: new HttpHeaders({
'Authorization': 'Bearer ' + refreshToken
})
};
return http.post<Tokens>(location.origin + '/LiveTime/services/v1/auth/tokens?locale=en', null, httpOptions);
}
promiseFromObservable(o): Promise<string> {
return new Promise((resolve, reject) => o.subscribe((token: Tokens) => resolve(token.token),reject(), err => { console.log(err); return null; }))
.then((token: Tokens) => {
localStorage.setItem('token', token.token);
localStorage.setItem('refreshToken', token.refreshToken);
this.refresh = false;
return token.token;
},
err => { console.log(err); return null; }
)
.catch((error) => { console.log(error);reject();
});
}
}
Can someone tell me what is wrong in this code?
I have two reactive methods in "Layer A" that's exposed to the app:
LAYER A - publicly accessible methods to the rest of the app
public Single<ResponseData> postMyData(ReqData data, Long id) {
if (!isUserLoggedIn()) return postLogin().andThen(postData(data, id));
return postData(data, id);
}
public Completable postLogin() {
Account account = getAccountData();
ReqLoginData loginData = new ReqLoginData(account.email, account.pass, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET);
Single<ResponseLogin> singlePostLogin = postLogin(loginData);
return Completable.create(subscriber -> singlePostLogin.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(loginResponse -> {
// Success
storeAccessToken(loginResponse);// *** this will recreate apiClient with new access token
if (!subscriber.isDisposed()) subscriber.onComplete();
}, throwable -> { if (!subscriber.isDisposed()) subscriber.onError(throwable);}
));
}
LAYER B - only accessible by Layer A
public Single<ResponseLogin> postLogin(ReqLoginData loginData) {
Log.d(TAG, "using apiClient "+apiClient.toString());
Single<Response<ResponseLogin>> postLogin = apiClient.postLogin(loginData);
return Single.create(subscriber -> postLogin.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(response -> {
if(response.body() != null) {
if (!subscriber.isDisposed()) subscriber.onSuccess(response.body());
} else {
if (!subscriber.isDisposed()) subscriber.onError(new Throwable(response.message()));
}
}, throwable -> {
throwable.printStackTrace();
if(!subscriber.isDisposed()) subscriber.onError(throwable);
}));
}
public Single<ResponseData> postData(ReqData data, Long id) {
Log.d(TAG, "using apiClient "+apiClient.toString());
Single<Response<ResponseData>> postData = apiClient.postData(id, data);
return Single.create(subscriber -> postData.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(response -> {
if(response.body() != null) {
if (!subscriber.isDisposed()) subscriber.onSuccess(response.body());
} else {
if (!subscriber.isDisposed()) subscriber.onError(new Throwable(response.message()));
}
}, throwable -> {
throwable.printStackTrace();
if(!subscriber.isDisposed()) subscriber.onError(throwable);
}));
}
LAYER C - API Interface
#Headers({"Content-Type: application/json"})
#POST(Constants.API_PREFIX + "/auth/token/")
Single<Response<ResponseLogin>> postLogin(#Body ReqLoginData reqLoginData);
#Headers({"Content-Type: application/json"})
#POST(Constants.API_PREFIX + "/data/{id}/")
Single<Response<ResponseData>> postData(#Path("id") Long id, #Body ReqData reqData);
A huge challenge for me at the moment is to make sure the user is logged-in before attempting to POST data. So, using postLogin().andThen(postData(data, id)) made sense to me :
At the time of my late-night development, it seemed that both methods postLogin() and postData() are called in parallel - with postLogin() first.
How can I make postMyData() execute postLogin() first, wait for success or failure, followed by postData() on success ?
Note: postLogin() will update apiClient with the new token inside storeAccessToken(). I now see that postData() doesn't use a reference to the new apiClient. It's important that postData() will use the new apiClient that's instantiated within storeAccessToken()
Sorry for the newbie question :-(
it turned out that Completable.andThen(Single) works very well to serialize code execution. Unfortunately, I didn't (half asleep) realize that only code within the create() method was actually scheduled.
my code below answers the problem I had (these are both in LAYER-B):
public Single<ResponseLogin> postLogin(ReqLoginData loginData) {
return Single.create(subscriber -> {
Log.d(TAG, "using apiClient "+apiClient.toString());
apiClient.postLogin(loginData)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(response -> {
if(response.isSuccessful() && response.body() != null) {
if (!subscriber.isDisposed()) subscriber.onSuccess(response.body());
} else {
if (!subscriber.isDisposed()) subscriber.onError(new Throwable(response.message()));
}
}, throwable -> {
throwable.printStackTrace();
if(!subscriber.isDisposed()) subscriber.onError(throwable);
});
});
}
public Single<ResponseData> postData(ReqData data, Long id) {
return Single.create(subscriber -> {
Log.d(TAG, "using apiClient "+apiClient.toString());
apiClient.postData(id, data)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(response -> {
if(response.body() != null) {
if (!subscriber.isDisposed()) subscriber.onSuccess(response.body());
} else {
if (!subscriber.isDisposed()) subscriber.onError(new Throwable(response.message()));
}
}, throwable -> {
throwable.printStackTrace();
if(!subscriber.isDisposed()) subscriber.onError(throwable);
});
});
}
I am trying to implement a catch for 401 responses and tried obtaining a refresh token based on Angular 4 Interceptor retry requests after token refresh. I was trying to implement the same thing, but I never was able to Retry that request, and I am really not sure if that is the best approach to apply the refresh token strategy.
Here is my code:
#Injectable()
export class AuthInterceptorService implements HttpInterceptor {
public authService;
refreshTokenInProgress = false;
tokenRefreshedSource = new Subject();
tokenRefreshed$ = this.tokenRefreshedSource.asObservable();
constructor(private router: Router, private injector: Injector) { }
authenticateRequest(req: HttpRequest<any>) {
const token = this.authService.getToken();
if (token != null) {
return req.clone({
headers: req.headers.set('Authorization', `Bearer ${token.access_token}`)
});
}
else {
return null;
}
}
refreshToken() {
if (this.refreshTokenInProgress) {
return new Observable(observer => {
this.tokenRefreshed$.subscribe(() => {
observer.next();
observer.complete();
});
});
} else {
this.refreshTokenInProgress = true;
return this.authService.refreshToken()
.do(() => {
this.refreshTokenInProgress = false;
this.tokenRefreshedSource.next();
}).catch(
(error) => {
console.log(error);
}
);
}
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.authService = this.injector.get(AuthenticationService);
request = this.authenticateRequest(request);
return next.handle(request).do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}, (err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
return this.refreshToken()
.switchMap(() => {
request = this.authenticateRequest(request);
console.log('*Repeating httpRequest*', request);
return next.handle(request);
})
.catch(() => {
return Observable.empty();
});
}
}
});
}
}
The issue is that SwitchMap is never reached in...
if (err.status === 401) {
return this.refreshToken()
.switchMap(() => {
and the do operator as well...
return this.authService.refreshToken()
.do(() => {
so that took me to my authService refreshToken method...
refreshToken() {
let refreshToken = this.getToken();
refreshToken.grant_type = 'refresh_token';
refreshToken.clientId = environment.appSettings.clientId;
return this.apiHelper.httpPost(url, refreshToken, null)
.map
(
response => {
this.setToken(response.data, refreshToken.email);
return this.getToken();
}
).catch(error => {
return Observable.throw('Please insert credentials');
});
}
}
It returns a mapped observable, and I know it needs a subscription if I replaced the do in...
return this.authService.refreshToken()
.do(() => {
With subscribe I'll break the observable chain I guess. I am lost and I've playing with this for a long time without a solution. :D
I'm glad that you like my solution. I'm going to put just the final solution here but if anybody wants to know the process that I fallowed go here: Refresh Token OAuth Authentication Angular 4+
Ok, First I created a Service to save the state of the refresh token request and Observable to know when the request is done.
This is my Service:
#Injectable()
export class RefreshTokenService {
public processing: boolean = false;
public storage: Subject<any> = new Subject<any>();
public publish(value: any) {
this.storage.next(value);
}
}
I noticed that It was better if I have two Interceptors one to refresh the token and handle that and one to put the Authorization Header if exist.
This the Interceptor for Refresh the Token:
#Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
constructor(private injector: Injector, private tokenService: RefreshTokenService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const auth = this.injector.get(OAuthService);
if (!auth.hasAuthorization() && auth.hasAuthorizationRefresh() && !this.tokenService.processing && request.url !== AUTHORIZE_URL) {
this.tokenService.processing = true;
return auth.refreshToken().flatMap(
(res: any) => {
auth.saveTokens(res);
this.tokenService.publish(res);
this.tokenService.processing = false;
return next.handle(request);
}
).catch(() => {
this.tokenService.publish({});
this.tokenService.processing = false;
return next.handle(request);
});
} else if (request.url === AUTHORIZE_URL) {
return next.handle(request);
}
if (this.tokenService.processing) {
return this.tokenService.storage.flatMap(
() => {
return next.handle(request);
}
);
} else {
return next.handle(request);
}
}
}
So here I'm waiting to the refresh token to be available or fails and then I release the request that needs the Authorization Header.
This is the Interceptor to put the Authorization Header:
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(private injector: Injector) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const auth = this.injector.get(OAuthService);
let req = request;
if (auth.hasAuthorization()) {
req = request.clone({
headers: request.headers.set('Authorization', auth.getHeaderAuthorization())
});
}
return next.handle(req).do(
() => {},
(error: any) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
auth.logOut();
}
}
});
}
}
And my main module is something like this:
#NgModule({
imports: [
...,
HttpClientModule
],
declarations: [
...
],
providers: [
...
OAuthService,
AuthService,
RefreshTokenService,
{
provide: HTTP_INTERCEPTORS,
useClass: RefreshTokenInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
Please any feedback will be welcome and if I'm doning something wrong tell me. I'm testing with Angular 4.4.6 but I don't know if it work on angular 5, I think should work.
Below interceptors do this task for you
import {
throwError as observableThrowError,
Observable,
Subject,
EMPTY,
} from 'rxjs';
import { catchError, switchMap, tap, finalize } from 'rxjs/operators';
import { Injectable } from '#angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpSentEvent,
HttpHeaderResponse,
HttpProgressEvent,
HttpResponse,
HttpUserEvent,
HttpErrorResponse,
} from '#angular/common/http';
import { StoreService } from './store.service';
import { ApiService } from './api.service';
export const tokenURL = '/315cfb2a-3fdf-48c3-921f-1d5209cb7861'; //copied from api service
#Injectable()
export class SessionInterceptorService implements HttpInterceptor {
isRefreshingToken: boolean = false;
cachedRequests = [];
tokenSubject: Subject<string> = new Subject<string>();
constructor(
private readonly store: StoreService,
private readonly ApiService: ApiService
) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<
| HttpSentEvent
| HttpHeaderResponse
| HttpProgressEvent
| HttpResponse<any>
| HttpUserEvent<any>
> {
let urlPresentIndex = this.cachedRequests.findIndex(
(httpRequest) => httpRequest.url == req.url
);
if (this.isRefreshingToken && !req.url.endsWith(tokenURL)) {
// check if unique url to be added in cachedRequest
if (urlPresentIndex == -1) {
this.cachedRequests.push(req);
return this.tokenSubject.pipe(
switchMap(() => next.handle(req)),
tap((v) => {
// delete request from catchedRequest if api gets called
this.cachedRequests.splice(
this.cachedRequests.findIndex(
(httpRequest) => httpRequest.url == req.url
),
1
);
return EMPTY;
})
);
} else {
//already in cached request array
return EMPTY;
}
}
return next.handle(this.updateHeader(req)).pipe(
catchError((error) => {
console.log(error);
if (error instanceof HttpErrorResponse) {
switch ((<HttpErrorResponse>error).status) {
case 400:
return this.handle400Error(error);
case 403 || 401:
if (req.url.endsWith(tokenURL)) {
return observableThrowError(error);
} else {
this.cachedRequests.push(req);
return this.handle401Error(req, next);
}
default:
return observableThrowError(error);
}
} else {
return observableThrowError(error);
}
})
);
}
handle400Error(error) {
if (
error &&
error.status === 400 &&
error.error &&
error.error.error === 'invalid_grant'
) {
// If we get a 400 and the error message is 'invalid_grant', the token is no longer valid so logout.
return this.logout();
}
return observableThrowError(error);
}
handle401Error(req: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
return this.ApiService.refreshToken().pipe(
switchMap((newToken: string) => {
if (newToken) {
this.store.updateAccessToken(newToken);
this.tokenSubject.next(newToken);
return next.handle(this.updateHeader(this.cachedRequests[0]));
}
// If we don't get a new token, we are in trouble so logout.
return this.logout();
}),
catchError((error) => {
// If there is an exception calling 'refreshToken', bad news so logout.
return this.logout();
}),
finalize(() => {
this.isRefreshingToken = false;
})
);
}
}
logout() {
console.log('logging it out');
// Route to the login page (implementation up to you)
return observableThrowError('');
}
/*
This method is append token in HTTP request'.
*/
updateHeader(req) {
const authToken = this.store.getAccessToken();
console.log(authToken);
req = req.clone({
headers: req.headers.set('X-RapidAPI-Key', `${authToken}`),
});
return req;
}
}
For more details you can read my medium article Token-Refresh-Interceptor-retry-failed-Requests
Check it out, how it works stackblitz
First, I must mention that I'm a beginner in Angular and I'm kind of stucked with my sample code.
I created some simple login app which prompts for username and password, calls login REST service (written in Java) that returns some token at login success or throws an exception at login failure.
Here's some of my code.
Login component:
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { AuthenticationService } from '../_services/index';
#Component({
moduleId: module.id,
templateUrl: 'login.component.html'
})
export class LoginComponent implements OnInit {
model: any = {};
error = '';
constructor(
private router: Router,
private authenticationService: AuthenticationService) { }
ngOnInit() {
// reset login status
this.authenticationService.logout();
}
login() {
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(result => {
if (result === true) {
this.router.navigate(['/']);
} else {
this.error = 'Login failed!';
}
},
err => {
this.error = 'Login failed!';
});
}
}
Authentication service:
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import { Observable } from 'rxjs';
import { CookieService } from 'angular2-cookie/core';
import { CookieOptionsArgs } from 'angular2-cookie/services/cookie-options-args.model';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthenticationService {
public token: string;
constructor(private http: Http, private cookieService: CookieService) {
// set token if saved in cookie
this.token = cookieService.get('token');
}
login(username, password): Observable<boolean> {
return this.http.post('http://localhost:9081/MyApp/login?username=' + username + '&password=' + password, new RequestOptions({}))
.map((response: Response) => {
// login successful if there's a token in the response
let token = response.text();
if (token !== '-1') {
// set token
this.token = token;
// store token in cookie to keep user logged
let opts: CookieOptionsArgs = {
path: '/'
};
this.cookieService.put('token', token, opts);
// return true to indicate successful login
return true;
} else {
// return false to indicate failed login
return false;
}
});
}
logout(): void {
// clear token, remove cookie to log user out
this.token= null;
this.cookieService.remove('token');
}
}
Everything works as expected. When login is successful, token is returned and I'm redirected to a "home" page. Otherwise, a "Login falied" message appears on a login page and no redirection occurs. What bothers me is that I don't exactly know why login fails: is it because username doesn't exist or is it maybe because password is wrong. What is the proper way to handle exceptions thrown by REST service? I assume that authentication service is the right place but I don't exactly know how to do it. I tried to extract some info from request object but request mapping doesn't happen if exception is thrown.
Thanks for help!
It seems you're looking for catching the exception occuring on error login in AuthenticationService . If it's the case add .catch section after .map, like in this subject :
best practives catching error Angualr 2
.catch((error: any) => { //catch Errors here using catch block
if (error.status === 500) {
// Display your message error here
}
else if (error.status === 400) {
// Display your message error here
}
});
i have implemented my code this way :
login(email: string, password: string): Observable<boolean> {
return new Observable(observer => {
var data = { email: email, password: password };
this.http.post(this.server_url + '/auth/authenticate', data).subscribe(x => {
var result = {
email: x.json().email,
token: x.json().token,
roles: x.json().roles.map(x => x.name)
}
localStorage.setItem(this._userKey, JSON.stringify(result));
observer.next(true);
observer.complete();
}, er => {
if (er.status == 401) {
observer.next(false);
observer.complete();
} else {
console.log(er);
observer.error(er);
observer.complete();
}
});
});
}
so it handle three possibilities :
if cridential is OK it returns true
if credential is wrong return false (remember your server must
return 401 status !)
otherwise there is problem in server and throw error
and in handler i got :
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(result => {
if (result == true) {
this.router.navigate(['/home']);
} else {
this.error = 'Username or password is incorrect';
this.loading = false;
}
}, err => {
this.error = 'Unexpected error occured.. please contact the administrator..';
this.loading = false;
});
}
Let's say I have following action:
export function signIn(data) {
return {
type: USER_SIGN_IN,
promise: api.post('/sign_in', data)
}
}
and following middleware:
export default function promiseMiddleware() {
return next => action => {
const { promise, type, ...rest } = action
if (!promise) {
return next(action)
}
const SUCCESS = `${type}_SUCCESS`
const REQUEST = `${type}_REQUEST`
const ERROR = `${type}_ERROR`
next({ type: REQUEST, ...rest })
return promise
.then(res => {
next({ response: res.data, type: SUCCESS, ...rest })
return true
})
.catch(error => {
...
})
}
}
This code is loosely based on https://github.com/reactGo/reactGo/
But what if in then callback after calling next I want to make a redirect to another path?
I did following. I passed redirect url through action:
export function signIn(data) {
return {
type: USER_SIGN_IN,
promise: api.post('/sign_in', data),
redirect: '/'
}
}
and added another call of next method with push from react-router-redux.
import { push } from 'react-router-redux'
export default function promiseMiddleware() {
return next => action => {
const { promise, type, redirect, ...rest } = action
...
return promise
.then(res => {
next({ response: res.data, type: SUCCESS, ...rest })
next(push(redirect))
return true
})
.catch(error => {
...
})
}
}
It seems like it works, but I'm not sure if this is a good idea or if there are some pitfalls of multiple next calls and I shouldn't do like this?
Maybe there are some better approaches for implementing such redirects?