I want to record the frequency of accessing the API. I want to insert every API access directly into the database through middleware. I don't know how to access the database in nestjs middleware.
The code will like:
import { NestMiddleware, Injectable } from '#nestjs/common';
import { Request, Response } from 'express';
// console.log('StatisticsMiddleware');
#Injectable()
export class StatisticsMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
// console.log(req.originalUrl, req.ip, req.connection.remoteAddress);
const url = req.originalUrl
const ip = req.ip || req.connection.remoteAddress
//
Db.insert('LOG_TABLE', url, ip)
next();
}
}
A middleware class is just like any other #Injectable() class in NestJS, and as such can have the database injected into it. The docs mention you can use injection like any other provider, so all you'll need to do is provide your database connection (be it a Mongo Model, a TypeORM Repository, or anything else) and you'll be good to access from there.
Related
I am new to Nestjs and I am using guards, strategies and passport for authentification.
I don't understand what's going on under the hood.
I have a guard for a refreshToken mutation:
import { AuthGuard } from '#nestjs/passport';
import { ExecutionContext } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
export class RtGuard extends AuthGuard('jwt-refresh') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
console.log('REFRESH');
return ctx.getContext().req;
}
}
What does this guard exactly do? Somehow it calls my strategy right? But it only does it, if I provide a correct refreshToken.
This is my Strategy:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, ForbiddenException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { JwtPayloadWithRt } from '../types/jwtPayloadWithRt.type';
import { JwtPayload } from 'src/auth/types/jwtPayload.type';
import { Request } from 'express';
#Injectable()
export class RefreshTokenStrategy extends PassportStrategy(
Strategy,
'jwt-refresh',
) {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.get<string>('REFRESH_TOKEN_SECRET'),
passReqToCallback: true,
});
}
validate(req: Request, payload: JwtPayload): JwtPayloadWithRt {
const refreshToken = req.get('authorization')?.replace('Bearer', '').trim();
if (!refreshToken) throw new ForbiddenException('Refresh token malformed');
return {
...payload,
refreshToken,
};
}
}
How is the guard able to decide whether my refresh token is the one in my database and if so, then it calls my strategy?
If I use a wrong refreshToken, not the one I got when I signed in, I get this error:
when providing the correct key, I get this:
Using console.log, I can see that my strategy is not called, whenever the refreshtoken is invalid.
How does the validation work exactly? How do guard and strategy work together under the hood?
Thanks a lot for help!
Named strategies
When implementing a strategy, you can provide a name for it by passing a second argument to the PassportStrategy function. If you don't do this, each strategy will have a default name
(e.g., 'jwt' for jwt-strategy):
export class JwtStrategy extends PassportStrategy (Strategy, 'myjwt')
Default name !== class name. The default name is imposed by the strategy you use.
For example, the default strategy for passport-jwt https://github.com/mikenicholson/passport-jwt is jwt
Source:
https://github.com/nestjs/nest/issues/4753#issuecomment-625675342
About how the guard is able to decide whether the token is in the database:
It doesn't. It just verifies that is valid, it is done using the secret key, which has to be the same that you signed the token with in the beggining. If it isn't valid it will throw a ForbiddenException thus the application never reaches the console.log('REFRESH') part of your code.
You could validate that it is in the db by your self, that's what the validate() method could be used for.
A quote from the nestjs docs:
It's also worth pointing out that this approach leaves us room
('hooks' as it were) to inject other business logic into the process.
For example, we could do a database lookup in our validate() method to
extract more information about the user, resulting in a more enriched
user object being available in our Request. This is also the place we
may decide to do further token validation, such as looking up the
userId in a list of revoked tokens, enabling us to perform token
revocation. The model we've implemented here in our sample code is a
fast, "stateless JWT" model, where each API call is immediately
authorized based on the presence of a valid JWT, and a small bit of
information about the requester (its userId and username) is available
in our Request pipeline.
Source: https://docs.nestjs.com/security/authentication#implementing-passport-jwt
validate(req: Request, payload: JwtPayload): JwtPayloadWithRt {
const refreshToken = req.get('authorization')?.replace('Bearer', '').trim();
if (!refreshToken) throw new ForbiddenException('Refresh token malformed');
/*
Perform database checks in this part of your code
*/
//Whatever you return here gets attached to the Request object as `req.user`,
//you can change it to whatever you want
return {
...payload,
refreshToken,
};
}
I am new in NestJS and trying to do auth system. I was able to do. So here is what I am doing to get access to auth.
In my controller I have
#Get('/user')
async getUser(#AuthUser() token: string) : Promise<Object> {
return this.authService.getUser(token)
return token
}
Here I am passing a AuthUser decorator I want to avoid passing in controllers.
In the authService.getUser method I have something like this
async getUser(token: string): Promise<Object> {
try {
const user = await this.jwtService.verifyAsync(token)
return user
} catch (error) {
return false
}
}
and my decorator looks like this
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const AuthUser = createParamDecorator(
(data = 'u_ses', ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return data ? request.cookies?.[data] : request.cookies;
},
);
I don't like code. If I need to know the user id from a service class or anywhere I would need to pass the token and to get token I need use #AuthUser() token: string)
So I want to do something like this
this.authService.getUser(), here I don't want to pass token or anything and should be able to access this getUser method from anywhere. Since it's a service class, I can inject and use it but I won't have the token.
I tried injecting the decorator inside the service class, but this doesn't work.
One best solution I would prefer is to use the JWT things inside the decorator, so I don't need the service class' method :)
I am looking for a nicer solutions from you :)
Thank you.
Nestjs has NestMiddleware. Here, you can authorize before access to controller like this:
import { Injectable, NestMiddleware, UnauthorizedException } from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const headerAuthentication = req.headers.authorization;
if(!headerAuthentication) throw new UnauthorizedException('Authorization failed!');
const token = req.headers.authorization.split(' ')[1];
if(token) {
next();
}else {
throw new UnauthorizedException('Authorization failed!');
}
}
}
and in AppModule implement it
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthenticationMiddleware).forRoutes('/');
}
}
I have built an app using ionic but my clients will be using different servers for accessing API. How can I give an option to set the base url by the user to call the desired server API?
There are 2 ways:
The temporary way:
This way, when the app is closed, it reset to the default api:
create a service ionic generate service
in this service, make a variable that will have the url you need
make some getter and setter
import this service where you need it (were you change your api, and in your api service)
The permanent way:
Use the file plugin to make, for example, a JSON that you will read/write with the api url in it.
set your base url in environment.ts file and use in any of service
export const environment = {
production: false,
baseUrl: 'http://localhost:3000/'
};
auth.service.ts
import { Injectable } from '#angular/core';
import { Observable} from 'rxjs';
import { HttpClient } from '#angular/common/http';
import { environment } from '../../../environments/environment';
#Injectable({
providedIn: 'root'
})
export class AuthService {
baseUrl = environment.baseUrl;
constructor(
private http: HttpClient
) { }
Userlogin(data: any): Observable<any> {
return this.http.post(this.baseUrl + 'user/login', data);
}
}
I'm currently working on a project using Aurelia as the front-end framework, and I'm wondering if there's a more eloquent and less redundant way to set the request header in my API services. The following is an example.
In this Post service, I have created a configureHeaders method that I'm calling prior to every API call because, otherwise, I run into the case where the web token has changed but the request header isn't updated. While creating this configureHeaders method is a functional workaround, I have to do it for each of my services, and it's feeling very redundant.
Is there a way to configure the request header application-wide so that I don't have to create a configureHeaders method for each service and call it for each request?
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-http-client';
import environment from 'environment';
#inject(HttpClient)
export class Post {
constructor(http) {
this.http = http;
}
configureHeaders() {
this.token = window.localStorage.getItem('token') || null;
this.http = this.http
.configure(x => {
x.withBaseUrl(environment.serverBaseURL);
x.withHeader('Authorization', `Bearer ${this.token}`);
});
}
getPosts() {
this.configureHeaders();
return this.http.get('post')
.then(posts => {
return JSON.parse(posts.response);
});
}
}
As R.Richards commented, Aurelia's HttpClient Interceptor is what you're after.
Here's a class example - as opposed to object with anonymous functions
1.) Declare the interceptor
import {Interceptor, HttpResponseMessage, RequestMessage} from 'aurelia-http-client'
export class CustomInterceptor implements Interceptor {
request(request: RequestMessage): RequestMessage {
//Do request interceptor here
return request;
}
response(response: HttpResponseMessage): HttpResponseMessage{
//Do response interception here
return response;
}
}
2.) Register the interceptor as part of your default http client within your main.js
import {CustomInterceptor} from 'path/to/custom-interceptor'
...
...
http.configure(config => {
//config stuff here
).withInterceptor(new CustomInterceptor())
This should suit your eloquence!
I'm setting up aurelia-auth and configured endpoints for my authorization server and a protected api:
aurelia.use.plugin('aurelia-api', configure => {
configure
.registerEndpoint('auth', 'http://localhost:5000/')
.registerEndpoint('api', 'http://localhost:5006')}
When I want to fetch data I inject the AuthService into my module and then call
this.authService.config.client.client.fetch('StaticData/offices')
but this calls against the auth endpoint not the api one, how do I tell the fetch client to use the non-default endpoint?
I was heading down the wrong path, you use the configuration object off aurelia-api to get an endpoint you can then call:
import { inject } from 'aurelia-framework';
import { Config } from 'aurelia-api'
#inject (Config)
export class Locations {
constructor (private apiEndpointConfig: Config)
{}
dataItems;
hasItems: boolean;
created(){
var api = this.apiEndpointConfig.getEndpoint('api');
api.client.fetch('StaticData/offices')
.then(response=>response.json())
.then(response=>
{
this.dataItems=response;
this.hasItems=true;
});
}
}