I'm trying to wrap the http service in angular 2 using this code:
#Injectable()
export class HttpSuperService {
private baseUrl: string;
constructor(private http: Http) {
}
get(url: string): Observable<string> {
return (
this.baseUrl ?
Observable.of(this.baseUrl) :
this.http.get('/config/').map((res: Response) => res)
)
.flatMap((res: any) => {
this.baseUrl = res._body;
return this.http.get(this.baseUrl + url)
.map((res2: Response) => res2.json());
});
}
}
What I'm trying to do is making the first request to get the baseUrl for the application (the API is on another URL) but only making that request once (the first time).
it works on the first request but the second request (IE when another component is using the same service) is not working since there is something wrong with the "Observable.of". I get that i need to use Response in some way instead of the string....That lead me to start thinking about this approach. Is there a better way to do this?
This solution works but feels a little to verbose since I plan to add other methods (POST, PUT) to this service as well:
#Injectable()
export class HttpSuperService {
private baseUrl: string;
constructor(private http: Http) {
}
get(url: string): Observable<string> {
if (this.baseUrl) {
return this.http.get(this.baseUrl + url).map((res2: Response) => res2.json());
}
return this.http.get('/config/').map((res: Response) => res)
.flatMap((res: any) => {
this.baseUrl = res._body;
return this.http.get(this.baseUrl + url).map((res2: Response) => res2.json());
});
}
}
You can factor out the dependency on the retrieval of the API url into dedicated method. That method can also handle some of the intermediate steps such as url concatenation to further reduce duplication.
For example:
import {Http} from '#angular/http';
import {Inject} from '#angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/do';
let baseUrl: string;
#Inject export class HttpSuperService {
routeUrl(url: string) {
return baseUrl
? Observable.of(baseUrl + url)
: this.http.get('/config/')
.do(response => baseUrl = response.text())
.mapTo(baseUrl + url);
}
constructor(readonly http: Http) {}
get(url: string): Observable<string> {
return this.routeUrl(url)
.flatMap(this.http.get)
.map(res => res.json());
}
post(url: string, body: Something): Observable<Something> {
return this.routeUrl(url)
.flatMap(url => this.http.post(url, body))
.map(response => <Something>response.json());
}
}
I think that is reasonable, but we can do better, we can be more DRY (there is no such thing as too dry :)):
#Inject export class HttpSuperService {
constructor(readonly http: Http) {}
routeUrl(url: string) {
return baseUrl
? Observable.of(baseUrl + url)
: this.http.get('/config/')
.do(response => baseUrl = response.text())
.mapTo(baseUrl + url);
}
request<R>(url: string, makeRequest: (url: string, body?: {}) => Observable<Response>) {
return this.routeUrl(url)
.flatMap(url => makeRequest(url))
.map(response => <R>response.json());
}
get(url: string): Observable<string> {
return this.request<string>(url, fullUrl => this.http.get(fullUrl));
}
post(url: string, body: Something): Observable<Something> {
return this.request<Something>(url, fullUrl => this.http.post(fullUrl, body));
}
}
I'm not entirely satisfied with the URL handling, and I would likely refactor it further, but we have cut down on duplication significantly.
I always use this method and works fine for me, I hope it be usefull :
in my service file :
getSomething(){
return this._http.get('YOUR-API-ENDPOINT').map(res=> res.json());
}
and in my component I use this to subscribe to Observable
return this._myServiceName.getSomething().subscribe(data =>{
this.muyData=data;
}
Related
I am working on an app in Angular 14 that requires authentication/authorization, reason for witch I use Keycloak Angular
.
I need to get the currently logged in user's data from the application.
For this purpose, I have a service:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { User } from '../../../models/user';
#Injectable({
providedIn: 'root'
})
export class UserFormService {
httpOptions: object = {
headers: new HttpHeaders({
'Content-Type' : 'application/json'
})
}
apiURL: string = 'http://localhost:8080';
constructor(private http: HttpClient) { }
public currentUserEmail: any;
public currentUserData: any;
public async getUserEmail(){
let currentUser = await this.keycloakService.loadUserProfile();
this.currentUserEmail = currentUser.email;
}
public getUserByEmail(email: string): Observable<User>{
return this.http.get<User>(`${this.apiURL}/getUserByEmail/${email}`, this.httpOptions);
}
}
I use it in a component:
public getUserByEmail() {
this.supplierFormService.getUserByEmail(this.currentUserEmail).subscribe(response => {
this.currentUser = response;
console.log('currentUser: ', response);
});
}
In keycloak.init.ts I have:
import { KeycloakService } from 'keycloak-angular';
export function initializeKeycloak(keycloak: KeycloakService) {
return () =>
keycloak.init({
config: {
url: 'http://localhost:8085',
realm: 'MyRealm',
clientId: 'my-app'
},
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/silent-check-sso.html'
}
});
}
ngOnInit(): void {
// Get user's email
this.getUserEmail();
// Get user's data by email
this.getUserByEmail();
}
The problem
Instad of returning the user's data, the service throws a 500 (Internal Server Error) and the email is undefined, as can be seen below:
http://localhost:8080/getUserByEmail?email=undefined
How do I fix this problem?
You should sync those two calls, the getUserByEmail may be excecuted faster then currentUserEmail is set:
async ngOnInit(): void {
// Get user's email
await this.getUserEmail();
// Get user's data by email
this.getUserByEmail();
}
decode jwt token returned from keycloak. It contains current user data and Id
Then get user by this id
I want to achieve something like this using nest.js:
(something very similar with Spring framework)
#Controller('/test')
class TestController {
#Get()
get(#Principal() principal: Principal) {
}
}
After hours of reading documentation, I found that nest.js supports creating custom decorator. So I decided to implement my own #Principal decorator. The decorator is responsible for retrieving access token from http header and get principal of user from my own auth service using the token.
import { createParamDecorator } from '#nestjs/common';
export const Principal = createParamDecorator((data: string, req) => {
const bearerToken = req.header.Authorization;
// parse.. and call my authService..
// how to call my authService here?
return null;
});
But the problem is that I have no idea how to get my service instance inside a decorator handler. Is it possible? And how? Thank you in advance
It is not possible to inject a service into your custom decorator.
Instead, you can create an AuthGuard that has access to your service. The guard can then add a property to the request object, which you can then access with your custom decorator:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const bearerToken = request.header.Authorization;
const user = await this.authService.authenticate(bearerToken);
request.principal = user;
// If you want to allow the request even if auth fails, always return true
return !!user;
}
}
import { createParamDecorator } from '#nestjs/common';
export const Principal = createParamDecorator((data: string, req) => {
return req.principal;
});
and then in your controller:
#Get()
#UseGuards(AuthGuard)
get(#Principal() principal: Principal) {
// ...
}
Note that nest offers some standard modules for authentication, see the docs.
for NestJS v7
Create custom pipe
// parse-token.pipe.ts
import { ArgumentMetadata, Injectable, PipeTransform } from '#nestjs/common';
import { AuthService } from './auth.service';
#Injectable()
export class ParseTokenPipe implements PipeTransform {
// inject any dependency
constructor(private authService: AuthService) {}
async transform(value: any, metadata: ArgumentMetadata) {
console.log('additional options', metadata.data);
return this.authService.parse(value);
}
}
Use this pipe with property decorator
// decorators.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
import { ParseTokenPipe} from './parse-token.pipe';
export const GetToken = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
return ctx.switchToHttp().getRequest().header.Authorization;
});
export const Principal = (additionalOptions?: any) => GetToken(additionalOptions, ParseTokenPipe);
Use this decorator with or without additional options
#Controller('/test')
class TestController {
#Get()
get(#Principal({hello: "world"}) principal) {}
}
You can use middlewar for all controller.
auth.middleware.ts
interface AccountData {
accId: string;
iat: number;
exp: number;
}
interface RequestWithAccountId extends Request {
accId: string;
}
#Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private readonly authenticationService: AuthenticationService) {}
async use(req: RequestWithAccountId, res: Response, next: NextFunction) {
const token =
req.body.token || req.query.token || req.headers['authorization'];
if (!token) {
throw new UnauthorizedException();
}
try {
const {
accId,
}: AccountData = await this.authenticationService.verifyToken(token);
req.accId = accId;
next();
} catch (err) {
throw new UnauthorizedException();
}
}
}
Then create AccountId decorator
account-id.decorator.ts
import {
createParamDecorator,
ExecutionContext,
UnauthorizedException,
} from '#nestjs/common';
export const AccountId = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest();
const token = req.accId;
if (!token) {
throw new UnauthorizedException();
}
return token;
},
);
Then apply AccountId decorator in your controller
your.controller.ts
#Get()
async someEndpoint(
#AccountId() accountId,
) {
console.log('accountId',accontId)
}
I'm coding the angular 5 app. There is refreshAccessToken in authentication service
refreshAccessToken(): Observable<ICredentials> {
const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
const httpParams = new HttpParams()
.append('grant_type', 'refresh_token')
.append('refresh_token', this.credentials.refresh_token)
.append('client_id', Constants.CLIENT_ID)
.toString();
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post(refreshTokenUrl, httpParams, { headers })
.map((response: any) => {
this.setCredentials(response);
localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
return response;
});
}
I want to implement next alghorithm:
Any http request failed because of unauthorized with status 401
Try to get new access token from server
Repeat the request
At the time while getting new access token, new http requests can be created, in this case I want to store them and repeat after new access token was recieved. To reach this purpose I've written the interceptor.
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { AuthenticationService } from '#app/core/authentication/authentication.service';
import { Urls, UrlsService } from '#app/shared/urls';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class UnauthorizedRequestInterceptor implements HttpInterceptor {
newAccessToken$: Observable<ICredentials> = null;
constructor(
public authService: AuthenticationService,
private router: Router,
private urlsService: UrlsService) {
}
addAuthHeader(request: HttpRequest<any>) {
if (this.authService.getCredentials()) {
return request.clone({
setHeaders: {
'Authorization': 'Bearer ' + this.authService.getCredentials().access_token
}
});
}
return request;
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = this.addAuthHeader(request);
return next.handle(request).catch((error: HttpErrorResponse) => {
let handleRequests$ = null;
if (this.isNeedNewAccessToken(error, request)) {
handleRequests$ = this.handleRequestWithNewAccessToken(request, next);
}
return handleRequests$ ||
(this.isUnathorizedError(error)
? Observable.empty()
: Observable.throw(error));
});
}
logout() {
this.authService.logout();
this.router.navigate(['login']);
}
private isNeedNewAccessToken(error: HttpErrorResponse, request: HttpRequest<any>): boolean {
return this.isUnathorizedError(error)
&& this.authService.isAuthenticated()
&& this.isSignInRequest(request);
}
private getNewAccessToken(): Observable<ICredentials> {
if (!this.newAccessToken$) {
this.newAccessToken$ = this.authService.refreshAccessToken();
}
return this.newAccessToken$;
}
private isUnathorizedError(error: HttpErrorResponse) {
return error.status === 401;
}
private handleRequestWithNewAccessToken(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
return this.getNewAccessToken()
.mergeMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
})
.catch((err: HttpErrorResponse) => {
if (err.error.error === 'invalid_grant') {
this.logout();
}
return Observable.empty();
});
}
private isNotSignInRequest(request: HttpRequest<any>): boolean {
return request.url !== this.urlsService.getUrl(Urls.TOKEN);
}
}
The behaviour of this interceptor is really strange. On each mergeMap on the handleRequestWithNewAccessTokenthe angular starts new post httpRequest. I've expected that the observable returned from refreshAccessToken(function from authenticationService, code at the top) would be resolved only once. I don't understand why it is fired for each merge map? I expected the next:
I have observable - http request for token
I use mergeMap - when http request finished, all callbacks that was added with mergeMap will be executed.
I was think to store requests that I need to handle in the global variable and invoke them in the subscribe() to http request, but there is problem, that each request should be resolved in the initial stream inside interceptor. I can't do smth like this: .subscribe(token => this.httpClient.request(storedRequest) because this will create new request, so all actions should be happened inside the observable chain.
Can you please help me to find solution?
PS This solution is working, but I want to get rid off unnecessary TOKEN requests, f.e. if page need to make 5 requests and token have expired - interceptor will make 5 requests for token.
I think your code is good and all you have to do is share the request for the new token.
refreshAccessToken(): Observable<ICredentials> {
const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
const httpParams = new HttpParams()
.append('grant_type', 'refresh_token')
.append('refresh_token', this.credentials.refresh_token)
.append('client_id', Constants.CLIENT_ID)
.toString();
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post(refreshTokenUrl, httpParams, { headers })
.map((response: any) => {
this.setCredentials(response);
localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
return response;
})
.share(); // <- HERE
}
Note share operator at the end of return
EDIT:
I also think you don't ever set back this.newAccessToken$ to null. Maybe consider adding set to null to finally like this:
private getNewAccessToken(): Observable<ICredentials> {
if (!this.newAccessToken$) {
this.newAccessToken$ = this.authService.refreshAccessToken()
.finally(() => {
this.newAccessToken$ = null;
});
}
return this.newAccessToken$;
}
Is it possible to make a redirect from a Nest controller without the usage of the #Response object?
For now I know that we can only do this via direct #Response object injection into the route handler.
You can write a RedirectInterceptor:
#Injectable()
export class RedirectInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
const response = context.switchToHttp().getResponse();
response.redirect('redirect-target');
return stream$;
}
}
Then use it in your controller like this:
#Get('user')
#UseInterceptors(RedirectInterceptor)
getUser() {
// will be redirected.
}
It is important not to return anything from your controller, otherwise you will get the following error: Can't set headers after they are sent.
If needed the RedirectInterceptor can be dynamic as well:
#Injectable()
export class RedirectInterceptor implements NestInterceptor {
constructor(private readonly target: string) {}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
const response = context.switchToHttp().getResponse();
response.redirect(this.target);
^^^^^^^^^^^
return stream$;
}
}
and then in the controller:
#UseInterceptors(new RedirectInterceptor('redirect-target'))
(a bit of a different implementation to another answer here...)
I created a RedirectError which can be thrown more dynamically than a decorator
import { ExceptionFilter, Catch, ArgumentsHost } from '#nestjs/common';
import { Response } from 'express';
export class RedirectError extends Error {
constructor(public readonly status: number, public readonly url: string) {
super();
}
}
#Catch(RedirectError)
export class RedirectFilter implements ExceptionFilter {
public catch(exception: RedirectError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
return response.redirect(exception.status, exception.url);
}
}
and then in main.ts set it:
app.useGlobalFilters(new RedirectFilter());
and finally to use it:
throw new RedirectError(302, '/some-target');
I've done it more complex, but I think it is good enough.
Create a class such as util/RedirectException like this:
The code like this:
import { HttpException, HttpStatus } from '#nestjs/common';
export class RedirectException extends HttpException {
constructor(message?: string | object) {
super(message, HttpStatus.CONTINUE);
}
}
Create a RedirectFilter by: nest g f RedirectFilter
Write the code like this:
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '#nestjs/common';
import { RedirectException } from './util/redirect-exception';
#Catch()
export class RedirectFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse(),
req = host.switchToHttp().getRequest();
try {
if (exception instanceof RedirectException) {
Object.keys(exception.message).forEach(k => {
req.session[k] = exception.message[k];
});
req.session.save(() => {
res.redirect(exception.message.url);
});
return;
}
if (exception instanceof HttpException) {
return res.status(exception.status).json(exception.message)
}
res.status(500).json({status: 500, message: 'Internal Server error'})
} catch (e) {
return res.status(500)
.json({
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: e.message
});
}
}
}
This class help you handle all the response when an exception is throw. And yes, this include the Redirect exception. Now we can throw the exception with exactly params, and it work!
Use the filter in main.ts: app.useGlobalFilters(new RedirectFilter());
And in controller, if you want to redirect to an url, just do this any time you want
Code:
throw new RedirectException({
url: 'the url you want to redirect',
field1: 'The first field you want to pass to session'
field2: 'The second field you want to pass to session'
});
Don't forget setup express-session if you want to pass data by session when redirect: https://www.npmjs.com/package/express-session.
If you don't want to use this, just replace the code inside if (exception instanceof RedirectException) {} to: res.redirect(exception.message.url);. It don't check and setup the session anymore.
I have a strange problem while using Angular 4 Observables.
I have created a ServiceProxy.ts that manages all my HTTPS calls for my app
#Injectable()
export class ServiceProxy
{
private base_url = 'https://localhost:8443';
constructor (private http:Http) {}
public Request(route:ServiceRegistry, data : any , protocol:HttpProtocol)
{
let url : string = this.FormURI(route);
let headers = new Headers();
this.createAuthorizationHeader(headers);
if(protocol==HttpProtocol.get)
{
return this.http.post(url , data , {headers: headers})
.map(this.extractData)
.catch(this.handleError);
}
else
{
return this.http.post(url , data , {headers: headers})
.map(this.extractData)
.catch(this.handleError);
}
}
}
Now I go ahead and INJECT this ServiceProxy class in every SERVICE which needs an HTTP calls
#Injectable()
export class AuthenticationService
{
constructor(private proxy:S.ServiceProxy){ }
attemptLogin(d:L.LoginAuth): Observable<any>
{
let r:S.ServiceRegistry =S.ServiceRegistry.STAFF_LOGIN;
let p: S.HttpProtocol = S.HttpProtocol.post;
return this.proxy.Request(r,d,p);
}
}
Once that is done. I call the authentication service from my component
this.authService.attemptLogin(payload).subscribe(response =>
{
alert("Subscription Received");
if(response.status==R.STATUS.OK)
{
this.HandleLogin(JSON.stringify(response.data));
}
else
{
this.HandleFailedLogin();
}
});
Problem is - The subscription function is being called two times instead of just once.
I understand, Promise would be a better fit here as this is just one HTTP call , however I want to standardize the interface with Observables and hence not considering Promises
Observable Chain is not proper, it's broken in AuthenticationService.
Modify AuthenticationService class
#Injectable()
export class AuthenticationService
{
constructor(private proxy:S.ServiceProxy){ }
attemptLogin(d:L.LoginAuth): Observable<any>
{
let r:S.ServiceRegistry =S.ServiceRegistry.STAFF_LOGIN;
let p: S.HttpProtocol = S.HttpProtocol.post;
return this.proxy.Request(r,d,p).map(
(data) => {
return data;
}
).catch(this.handleError);
}
}