API calls from one angular service is not being intercepted by jwt -interceptor - while all others are ok - angular-http-interceptors

In my angular-ionic app I have several services calling REST apis in the backend.
All API calls are intercepted by jwt-interceptor and bearer token is being added except 1 .
The funny thing is that one method (getVehicles) is begin intercepted correctly while the concerned method (createVehicle) is not being intercepted. Any ideas?
Pls find code for my service
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
import { VehicleModel } from '../models/vehicle.model';
import { environment } from 'src/environments/environment';
import { Criteria } from 'src/app/common/model/criteria.model';
#Injectable({
providedIn: 'root'
})
export class VehicleService {
constructor(private httpClient: HttpClient) { }
criteria: Criteria;
vehicle: VehicleModel;
baseUrl = environment.API_URL + '/api/vehicles';
vehicleRegistrationNumber: string;
// List of vehicles
public getVehicles(criteria: Criteria): Observable<VehicleModel[]> {
return this.httpClient.post<VehicleModel[]>(this.baseUrl + '/search', criteria);
}
// Create a new item
public createVehicle(vehicle: VehicleModel): Observable<VehicleModel> {
return this.httpClient.post<VehicleModel>(this.baseUrl + '/createVehicle', vehicle);
}
// Edit vehicle details
public updateVehicle(vehicle: VehicleModel): Observable<any> {
return this.httpClient.put<VehicleModel>(
this.baseUrl + '/updateVehicle', vehicle);
}
}
and code for jwt-inteceptor:
import { Injectable } from '#angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '#angular/common/http';
import { Observable } from 'rxjs';
import { AuthenticationService } from '#/_shared/_services';
#Injectable({ providedIn: 'root' })
export class JwtInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthenticationService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization header with jwt token if available
const currentUser = this.authenticationService.currentUserValue;
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request);
}
}

Related

Trouble with module import [ NEST JS ]

I'm trying to use a jwt.strategy.ts to protect my endpoints with jwt verification. Everything was going ok until I decided to import a custom JWTService inside this jwt.strategy which contains its own JWTModule but Nest doesn't seems to recognize it. I can use the JWTService in other services but it doesn't work inside the strategy. What should I do ? What am I doing wrong ?
The NEST Message:
[Nest] 53 - 09/22/2022, 6:32:25 PM ERROR [ExceptionHandler] Nest can't resolve dependencies of the JwtStrategy (ConfigService, ?). Please make sure that the argument Object at index [1] is available in the AuthModule context.
Potential solutions:
- If Object is a provider, is it part of the current AuthModule?
- If Object is exported from a separate #Module, is that module imported within AuthModule?
#Module({
imports: [ /* the Module containing Object */ ]
})
Error: Nest can't resolve dependencies of the JwtStrategy (ConfigService, ?). Please make sure that the argument Object at index [1] is available in the AuthModule context.
Potential solutions:
- If Object is a provider, is it part of the current AuthModule?
- If Object is exported from a separate #Module, is that module imported within AuthModule?
#Module({
imports: [ /* the Module containing Object */ ]
})
The JWTModule:
import { Module } from '#nestjs/common';
import { JwtModule } from '#nestjs/jwt';
import { jwtOptions } from './jwt.config';
import { JWTService } from './jwt.service';
#Module({
imports: [JwtModule.registerAsync(jwtOptions)],
providers: [JWTService],
exports: [JWTService],
})
export class JWTModule {}
The JWTService:
import { Request } from 'express';
import { DecodeOptions } from 'jsonwebtoken';
import { Injectable, UnprocessableEntityException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { JwtService, JwtSignOptions, JwtVerifyOptions } from '#nestjs/jwt';
import { CookieHttpConfig } from '../auth';
#Injectable()
export class JWTService {
constructor(
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {}
sign(payload: string | object | Buffer, options?: JwtSignOptions) {
return this.jwtService.sign(payload, options);
}
async signAsync(payload: string | object | Buffer, options?: JwtSignOptions) {
return this.jwtService.signAsync(payload, options);
}
verify(token: string, options?: JwtVerifyOptions) {
return this.jwtService.verify(token, options);
}
async verifyAsync(token: string, options?: JwtVerifyOptions) {
return this.jwtService.verifyAsync(token, options);
}
decode(token: string, options?: DecodeOptions) {
return this.jwtService.decode(token, options);
}
async getToken(tokenPayload: any): Promise<string> {
try {
const token: string = await this.jwtService.signAsync(tokenPayload);
return `Bearer ${token}`;
} catch (error) {
throw new UnprocessableEntityException(error.message);
}
}
async refreshToken(
cookieName: string,
request: Request,
payload: any,
): Promise<void> {
const token: string = await this.getToken(payload);
request.res.cookie(cookieName, token, CookieHttpConfig.Options());
}
}
jwt.strategy:
import { JWTService } from '#app/common';
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { Request } from 'express';
import { ExtractJwt, Strategy } from 'passport-jwt';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService,
private readonly jwtService: JWTService,
) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
try {
const token = request.signedCookies['Authorization'].split(' ')[1];
return token;
} catch (error) {
return null;
}
},
]),
ignoreExpiration: false,
secretOrKey: configService.get('AUTH_JWT_SECRET'),
});
}
async validate(request: Request, payload: any): Promise<any> {
const tokenPayload = {
email: payload.email,
id: payload.id,
};
await this.jwtService.refreshToken('Authorization', request, tokenPayload);
return tokenPayload;
}
}
Extra information about my project:
I divided the project into a monorepo, so I imported the JWTModule inside the AuthModule but it still doesn't work. The jwt.strategy.ts and the JWTModule is inside a shared lib created at the same level as the apps folders containing the microservices.
The AuthModule:
import { Module } from '#nestjs/common';
import { PassportModule } from '#nestjs/passport';
import { UsersModule } from '../users/users.module';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import {
AuthLibModule,
JWTModule,
JwtStrategy,
LocalStrategy,
} from '#app/common';
import { Web3Module } from '../web3/web3.module';
import { VonageModule } from '#app/common';
#Module({
imports: [
UsersModule,
PassportModule,
JWTModule,
Web3Module,
VonageModule,
AuthLibModule,
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
})
export class AuthModule {}

NestJS Fastify JWKS Validation

I am using the Fastify Adapter in my NestJS application and would like to add some logic to do JWKS validation, similar to the passport example on the Auth0 website.
// src/authz/jwt.strategy.ts
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
import * as dotenv from 'dotenv';
dotenv.config();
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${process.env.AUTH0_ISSUER_URL}.well-known/jwks.json`,
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: process.env.AUTH0_AUDIENCE,
issuer: `${process.env.AUTH0_ISSUER_URL}`,
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
return payload;
}
}
It is my understanding that Passport only works with Express and will not work with Fastify. Does anyone know how to do something like this with Fastify and NestJS ?
I didn't manage to find a library like passport to do the JWKS validation with fastify. I decided to write my own validation using the jsonwebtoken and the #types/jsonwebtoken libraries.
Below is a sample of my solution for anybody else that is interested :)
File structure is as follows:
src
|__ auth
|__jwks
|_ jwks.client.ts
|_ jwks.service.ts
|_ jwt-auth.guard.ts
|_ jwt-auth.module.ts
|__ caching
|_ redis-cache.module.ts
|__ models
|__ json-web-key.model.ts
|__ jwks-response.model.ts
|__ my.controller.ts
|__ app.module.ts
models for the jwks response
// src/models/jwks-response.model.ts
import { JsonWebKey } from "src/models/json-web-key.model";
export class JwksResponse {
keys: Array<JsonWebKey>
}
// src/models/json-web-key.model.ts
export class JsonWebKey {
kty: string;
kid: string;
use: string;
x5t: string;
x5c: Array<string>;
n?: string;
e?: string;
x?: string;
y?: string;
crv?: string;
}
client to call the jwks endpoint and process the response
//src/auth/jwks/jwks.client.ts
import { HttpException, Injectable, Logger } from "#nestjs/common";
import { ConfigService} from "#nestjs/config";
import { HttpService } from "#nestjs/axios";
import { map, lastValueFrom } from "rxjs";
import { JwksResponse } from "src/models/jwks-response.model";
import { JsonWebKey } from "src/models/json-web-key.model";
#Injectable()
export class JwksClient {
private readonly logger: Logger = new Logger(JwksClient.name);
private readonly JWKS_URL: string = this.configService.get<string>('services.jwks.url');
private readonly TIMEOUT: number = parseInt(this.configService.get<string>('services.timeout'));
constructor(private configService: ConfigService, private httpService: HttpService){}
async getJsonWebKeySet(): Promise<Array<JsonWebKey>> {
this.logger.log(`Attempting to retrieve json web keys from Jwks endpoint`);
const config = {
timeout: this.TIMEOUT,
};
let response: JwksResponse = null;
try {
response = await lastValueFrom(this.httpService.get(this.JWKS_URL, config)
.pipe(
map((response) => {
return response.data;
},
),
),
);
} catch(e) {
this.logger.error(`An error occurred invoking Jwks endpoint to retrieve public keys`);
this.logger.error(e.stack);
throw new HttpException(e.message, e.response.status);
}
if (!response || !response.keys || response.keys.length == 0) {
this.logger.error('No json web keys were returned from Jwks endpoint')
return [];
}
return response.keys;
}
}
service containing logic to call jwks endpoint and verify the jwt token with the public key.
The JWT token will consist of a header, payload and a signature.
The header should also have a kid field that will match the kid of one of the json web keys, so that you know which one to verify your token with.
The x5c array contains a certificate chain and the first element of this array will always contain the certificate that you use to get the public key from to verify the token.
Note: I had to wrap the certificate in with \n-----BEGIN CERTIFICATE-----\n${key.x5c[0]}\n-----END CERTIFICATE----- to be able to create the public key but you may not have to do this in your implementation.
You will also need to add logic to verify the claims for your JWT.
I have also cached a valid JWT for a period of time to ensure that the verification is not required each time as this would have performance implications, the key for this cache uses the auth token to ensure that it is unique.
import { HttpException, HttpStatus, Injectable, CACHE_MANAGER, Logger, Inject } from "#nestjs/common";
import { ConfigService} from "#nestjs/config";
import { IncomingHttpHeaders } from "http";
import { JwksClient } from "src/auth/jwks/jwks.client";
import { JsonWebKey } from "src/models/json-web-key.model";
import { JwtPayload } from 'jsonwebtoken';
import * as jwt from 'jsonwebtoken';
import * as crypto from "crypto";
import { Cache } from 'cache-manager';
#Injectable()
export class JwksService {
private readonly logger: Logger = new Logger(JwksService.name);
private readonly CACHE_KEY: string = this.configService.get<string>('caches.jwks.key');
private readonly CACHE_TTL: number = parseInt(this.configService.get<string>('caches.jwks.ttl'));
constructor(private configService: ConfigService, private readonly jwksClient: JwksClient, #Inject(CACHE_MANAGER) private cacheManager: Cache){}
async verify(request: any): Promise<boolean> {
let token: string = this.getAuthorizationTokenFromHeader(request.headers);
const jwksKey = `${this.CACHE_KEY}:${token}`
const cachedVerificationResult: boolean = await this.getCachedVerificationResult(jwksKey);
if (cachedVerificationResult) {
this.logger.debug("Found cached verification result");
return cachedVerificationResult;
}
if (!this.hasTokenWithValidClaims(token)) {
this.logger.error("Token with invalid claims was provided")
return false;
}
// Get all web keys from JWKS endpoint
let jsonWebKeys: Array<JsonWebKey> = await this.jwksClient.getJsonWebKeySet();
// Find the public key with matching kid
let publicKey: string | Buffer = this.findPublicKey(token, jsonWebKeys);
if (!publicKey) {
this.logger.error("No public key was found for the bearer token provided")
return false;
}
try {
jwt.verify(token, publicKey, { algorithms: ['Put algorithm here e.g. HS256, ES256 etc'] });
} catch(e) {
this.logger.error("An error occurred verifying the bearer token with the associated public key");
this.logger.error(e.stack);
throw new HttpException(e.message, HttpStatus.FORBIDDEN);
}
// Cache Jwks validation result
this.cacheManager.set(jwksKey, true, { ttl: this.CACHE_TTL });
this.logger.debug("Successfully verified bearer token with the associated public key")
return true;
}
private hasTokenWithValidClaims(token: string) {
var { header, payload, signature } = jwt.decode(token, { complete: true });
// TODO: Add validation for claims
return true;
}
private findPublicKey(token: string, jsonWebKeys: Array<JsonWebKey>): string | Buffer {
var { header } = jwt.decode(token, { complete: true });
let key = null;
for (var jsonWebKey of jsonWebKeys) {
if (jsonWebKey.kid === header.kid) {
this.logger.debug(`Found json web key for kid ${header.kid}`);
key = jsonWebKey;
break;
}
}
if (!key) {
return null;
}
// Exctact x509 certificate from the certificate chain
const x509Certificate = `\n-----BEGIN CERTIFICATE-----\n${key.x5c[0]}\n-----END CERTIFICATE-----`;
// Create the public key from the x509 certificate
return crypto.createPublicKey(x509Certificate).export({type:'spki', format:'pem'})
}
private getAuthorizationTokenFromHeader(headers: IncomingHttpHeaders): string {
if(!headers || !headers.authorization) {
throw new HttpException("Authorization header is missing", HttpStatus.BAD_REQUEST);
}
let token: string = headers.authorization;
if (token.startsWith("Bearer ")) {
token = headers.authorization.split(" ")[1].trim();
}
return token;
}
private async getCachedVerificationResult(jwksKey: string): Promise<boolean> {
const response: boolean = await this.cacheManager.get(jwksKey);
if(response && response === true) {
return response;
}
return null;
}
}
guard to verify the JWT
// src/auth/jwks/jwt-auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, Logger } from '#nestjs/common';
import { JwksService } from 'src/auth/jwks/jwks.service';
#Injectable()
export class JwtAuthGuard implements CanActivate {
private readonly logger: Logger = new Logger(JwtAuthGuard.name);
constructor(private jwksService: JwksService){}
async canActivate(
context: ExecutionContext,
): Promise<boolean> {
const request = context.switchToHttp().getRequest();
return await this.jwksService.verify(request);
}
}
module containing config for jwks
// src/auth/jwks/jwt-auth.model.ts
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { HttpModule } from '#nestjs/axios';
import configuration from '../../../config/configuration';
import { JwksClient } from 'src/auth/jwks/jwks.client';
import { JwksService } from 'src/auth/jwks/jwks.service';
#Module({
imports: [
ConfigModule.forRoot({ load: [configuration] }),
HttpModule
],
providers: [
JwksClient,
JwksService,
],
exports: [JwksService, JwksClient],
})
export class JwtAuthModule {}
redis caching module containing config for redis cache
// src/caching/redis-cache.module.ts
import { CacheModule, Module } from '#nestjs/common';
import { ConfigModule, ConfigService } from '#nestjs/config';
import configuration from '../../config/configuration';
import { RedisClientOptions } from 'redis';
import * as redisStore from 'cache-manager-redis-store';
#Module({
imports: [
ConfigModule.forRoot({ load: [configuration] }),
CacheModule.registerAsync<RedisClientOptions>({
isGlobal: true,
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
store: redisStore,
host: process.env.REDIS_URL,
port: configService.get<number>('redis.port'),
password: configService.get<string>('redis.password'),
tls: configService.get<boolean>('redis.tls')
}),
inject: [ConfigService],
})
],
controllers: [],
providers: []
})
export class RedisCacheModule {}
controller that uses the JwtAuthGuard
// src/my.controller.ts
import { Controller, Get, Param, Logger } from '#nestjs/common';
#Controller()
#UseGuards(JwtAuthGuard)
export class MyController {
private readonly logger: Logger = new Logger(MyController.name);
#Get('/:id')
async getCustomerDetails(#Headers() headers, #Param('id') id: string): Promise<Customer> {
this.logger.log(`Accepted incoming request with id: ${id}`);
// Do some processing ....
return customer;
}
}
module containing configuration for whole app
// src/app.module.ts
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { HttpModule } from '#nestjs/axios';
import configuration from '../config/configuration';
import { JwtAuthModule } from 'src/auth/jwks/jwt-auth.module';
import { RedisCacheModule } from 'src/caching/redis-cache.module';
#Module({
imports: [
ConfigModule.forRoot({ load: [configuration] }),
HttpModule,
JwtAuthModule,
RedisCacheModule
],
controllers: [MyController],
providers: []
})
export class AppModule {}

Rest web service returns null value on mobile device but works on PC in Ionic App

i am consuming Rest JSONP Web Service in an ionic App which works fine on PC but returns null value on mobile devices
My page.ts file
import {
Component,
OnInit
} from '#angular/core';
import {
AlertController,
LoadingController
} from '#ionic/angular';
import {
ActionSheetController
} from '#ionic/angular';
import {
Router
} from '#angular/router'
import {
ProApiService
} from './../../../../services/pro-api.service';
#Component({
selector: 'app-ranked-diagnosis',
templateUrl: './ranked-diagnosis.page.html',
styleUrls: ['./ranked-diagnosis.page.scss'],
})
export class RankedDiagnosisPage implements OnInit {
tabSelect: string = 'show10';
show10Data: Array < any >= [];
showAllData: Array < any >= [];
redFlagsData: Array < any >= [];
loading: any;
constructor(
private api: ProApiService,
public alertController: AlertController,
public loadingController: LoadingController,
public actionSheetController: ActionSheetController,
private router: Router
) {}
segmentChanged(event: any) {
this.tabSelect = event.detail.value;
}
async presentActionSheet(buttons) {
const actionSheet = await this.actionSheetController.create({
header: 'Sub Diagnosis',
buttons: buttons
});
await actionSheet.present();
}
async presentAlert(msg: string, header: string) {
const alert = await this.alertController.create({
header: '',
subHeader: header,
message: msg,
buttons: ['OK']
});
await alert.present();
}
async presentLoading() {
this.loading = await this.loadingController.create({
message: 'loading...',
});
return await this.loading.present();
}
ngOnInit() {
}
ionViewWillEnter() {
if (this.api.ProApiData.diagnoses_checklist.diagnoses) {
this.showAllData =
this.api.ProApiData.diagnoses_checklist.diagnoses;
for (let i = 0; i < 10; i++) {
this.show10Data.push(this.showAllData[i]);
}
this.showAllData.forEach(item => {
if (item.red_flag == 'true') {
this.redFlagsData.push(item);
}
});
console.log(this.showAllData);
} else {
console.log('error');
this.router.navigateByUrl('isabel-pro');
}
}
why_diagnosis(url: any, weightage: any) {
this.presentLoading();
this.api.why_diagnosisApi(url).subscribe(res => {
let matched_terms = res._body.why_diagnosis.matched_terms;
console.log(matched_terms);
let alertMsg = `We matched the terms: ${matched_terms}<br><hr>Degree of match between query entered and database: ${weightage}`;
this.presentAlert(alertMsg, 'Why did this diagnosis come up ?');
this.loadingController.dismiss();
}, err => {
this.loadingController.dismiss();
console.log('error');
});
}
}
in the above code i am calling why_diagnosis function which calls the function from a service file.
My service.ts file
import {
Injectable
} from '#angular/core';
import {
HttpClient,
HttpHeaders
} from '#angular/common/http';
import {
Jsonp
} from '#angular/http';
import {
Observable
} from 'rxjs';
import {
map
} from 'rxjs/operators';
import {
ConstantsService
} from './../../../services/constants.service';
#Injectable({
providedIn: 'root'
})
export class ProApiService {
apiRoot = this.root.APIroot;
diagnosisPROData: any;
drugData: any;
ProApiData: any;
drugApiData: any;
constructor(
private jsonp: Jsonp,
private http: HttpClient,
private root: ConstantsService) {}
why_diagnosisApi(url: any): Observable < any > {
let whyUrl = `${this.apiRoot}Mob_isabelPRO.php?
why_url=${url}&callback=JSONP_CALLBACK`;
return this.jsonp.request(whyUrl, 'callback')
.pipe(
map(
res => {
let why_diagnosis = res;
return why_diagnosis;
}
)
);
}
}
above code is from my service file.
this is the value i am getting in PC
this is the return on mobile
i dont know whats wrong with it. please suggest me the solution
Thanks

Ionic 3 + HttpClientModule and token from storage

I have build an interceptor for making HTTP requests to a PHP backend.
This backend gives an JWT token to the app and I save this in the Ionic Storage.
But I want to get that token from Storage and add it as an header to the HTTP request.
Below is my interceptor with and hardcoded token.
This works and I get a response from the backend.
See update # bottom of this post
http-interceptor.ts
import { HttpInterceptor, HttpRequest } from '#angular/common/http/';
import {HttpEvent, HttpHandler} from '#angular/common/http';
import { AuthProvider } from "../providers/auth/auth";
import {Injectable} from "#angular/core";
import {Observable} from "rxjs/Observable";
import {Storage} from "#ionic/storage";
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const changedReq = req.clone({headers: req.headers.set('Authorization', 'Bearer MY TOKEN')});
return next.handle(changedReq);
}
}
But how do I get the token from storage into the header.
I searched alot and most of the tutorials / examples are from the older HTTP module. If someone has an idea or has a up2date example ?
UPDATE
Oke below code send the token
intercept(req: HttpRequest<any>, next: HttpHandler) : Observable<HttpEvent<any>>{
return fromPromise(this.Auth.getToken())
.switchMap(token => {
const changedReq = req.clone({headers: req.headers.set('Authorization', 'Bearer ' + token )});
return next.handle(changedReq);
});
}
With 1 exception, namely the first time I access that page :)
You can save JWT token in, for example, localStorage
localStorage.setItem('myToken', res.token);
and then access it with
localStorage.getItem('myToken');
In your case something like this:
import { HttpInterceptor, HttpRequest } from '#angular/common/http/';
import {HttpEvent, HttpHandler} from '#angular/common/http';
import { AuthProvider } from "../providers/auth/auth";
import {Injectable} from "#angular/core";
import {Observable} from "rxjs/Observable";
import {Storage} from "#ionic/storage";
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const changedReq = req.clone({headers: req.headers.set('Authorization', localStorage.getItem('myToken'))});
return next.handle(changedReq);
}
}
If you want to use Ionic Storage
import { HttpInterceptor, HttpRequest } from '#angular/common/http/';
import {HttpEvent, HttpHandler} from '#angular/common/http';
import { AuthProvider } from "../providers/auth/auth";
import {Injectable} from "#angular/core";
import {Observable} from "rxjs/Observable";
import {Storage} from "#ionic/storage";
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(public _storage: Storage) {
_storage.get('myToken').then((val) => {
console.log('Your age is', val);
});
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const changedReq = req.clone({headers: req.headers.set('Authorization', this.val)});
return next.handle(changedReq);
}
}
Caching the token in the interceptor is a bad idea because if the token changes the interceptor will not be aware of those changes.
// Don't do this.
token: string;
constructor(private storage: Storage) {
this.storage.get('token').then((res) => {
this.token = res;
})
}
If you want to use Ionic Storage and the interceptor together you can do so by using Observable.flatMap like so...
app.module.ts
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
SecurityStorageService
]
AuthInterceptor.ts
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
private securityStorageService: SecurityStorageService
) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// This method gets a little tricky because the security token is behind a
// promise (which we convert to an observable). So we need to concat the
// observables.
// 1. Get the token then use .map to return a request with the token populated in the header.
// 2. Use .flatMap to concat the tokenObservable and next (httpHandler)
// 3. .do will execute when the request returns
const tokenObservable = this.securityStorageService.getSecurityTokenAsObservable().map(token => {
return request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
});
return tokenObservable.flatMap((req) => {
return next.handle(req).do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff to the response here
}
}, (err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
// not authorized error .. do something
}
}
});
})
}
security-storage-service.ts
You technically don't need this service, but you shouldn't have Ionic Storage logic in your interceptor.
#Injectable()
export class SecurityStorageService {
constructor(private storage: Storage) {
}
getSecurityToken() {
return this.storage.get(StorageKeys.SecurityToken)
.then(
data => { return data },
error => console.error(error)
);
}
getSecurityTokenAsObservable() {
return Observable.fromPromise(this.getSecurityToken());
}
}
storage-keys.ts
export class StorageKeys {
public static readonly SecurityToken: string = "SecurityToken";
}
For anyone who comes across this like me and is using rxjs >=5.5.0 then you can just do:
auth-interceptor.ts
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return from(this.authService.getToken()).pipe(mergeMap((token) => {
const changedReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next.handle(changedReq);
}));
}
auth-service.ts
public async getToken() {
return await this.storage.get('ACCESS_TOKEN');
}

Angular2: strange vague error when trying to get Json from server using a rest service

I am trying to get a product from a local server using an angular2 rest service. When I do the GET I get the following error. I can get the item from the server using the Insomnia rest client so I know the issue is not with the server. i've also checked the url and made sure there's no errors there. Any idea what else I can check? i'm using ng-cli to run the app...
my error:
http://10.60.160.34/BRMServices/WebEnquiry//POSEnquiry/293
Failed to load resource: net::ERR_CONNECTION_RESET
app.component.ts:32
failureServer error
my rest service:
import { Injectable } from '#angular/core';
import { Http, Headers, Response, RequestOptions } from "#angular/http";
import { Observable } from "rxjs/Rx";
import { ProductModel } from "../models/product.model";
//import 'rxjs/add/operator/map';
//import 'rxjs/add/operator/catch';
//import 'rxjs/add/observable/throw'
#Injectable()
export class RestService {
public API_URL: string = "http://10.60.160.34/BRMServices/WebEnquiry/";
private headers: Headers;
private options: RequestOptions;
constructor(private http: Http){
this.init();
}
init() {
this.headers = new Headers({ 'Content-Type': 'application/json' });
this.options = new RequestOptions({ headers: this.headers });
}
getProduct(barcode: string): Observable<ProductModel> {
return this.http.get(this.API_URL + "/POSEnquiry/" + barcode, this.options)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
}
my app.component.ts:
import { Component } from '#angular/core';
import { RestService } from "./services/rest.service";
import { ProductModel } from "./models/product.model";
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
product: ProductModel;
constructor(private restService: RestService){}
submitBarcode(barcode: HTMLInputElement){
this.restService.getProduct(barcode.value)
.subscribe((res) => {
//product = res;
console.log(res);
}, (res) => {
console.log("failure" + res);
});
//console.log("product: " + product);
}
}
It turned out to be the RequestOptions variable. When I replaced options with headers in the get request it worked with no issues...