I am implementing linkedin login strategy with passport in nestJS.
What I have right now is that I have a button "Login with linkedin" points to auth/linkedin.
#Get('auth/linkedin')
#UseGuards(AuthGuard('linkedin'))
async linkedinAuth(#Req() req) {
}
Which works great and takes me to the linkedin login page and hits back my callback URL which is auth/linkedin/callback with code query string of token and this is where I am unable to figure out what todo and how to return linkedin user
#Get('auth/linkedin/callback')
#UseGuards(AuthGuard('linkedin'))
linkedinCallBack(#Req() req) {
console.log(req)
return 'handle callback!';
}
Linkedin passport strategy :
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
const LinkedInAuthStrategy = require('passport-linkedin-oauth2').Strategy;
#Injectable()
export class LinkedinStrategy extends PassportStrategy(LinkedInAuthStrategy) {
constructor(
) {
super({
clientID: 'abcd123',
clientSecret: 'abcd123',
callbackURL: 'http://localhost:5000/auth/linkedin/callback',
scope: ['r_emailaddress', 'r_liteprofile'],
}, function(accessToken, refreshToken, profile, done) {
process.nextTick(function () {
console.log(profile);
return done(null, profile);
});
})
}
}
Note: I am using this package for linkedin passport strategy
Question : How can I handle callback further with #UseGuards, and return LinkedIn user?
You should adapt the LinkedinStrategy class a bit. You can't use the done function directly. It will be called by nest. You should have a validate method and return a user object from it. That object will be set to the request object so in the controller you will be able to access it with req.user. This is approximately how your class should look:
import { Strategy } from 'passport-linkedin-oauth2';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthService } from './auth.service';
#Injectable()
export class LinkedinStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
clientID: 'abcd123',
clientSecret: 'abcd123',
callbackURL: 'http://localhost:5000/auth/linkedin/callback',
scope: ['r_emailaddress', 'r_liteprofile'],
});
}
async validate(accessToken: string, refreshToken: string, profile: object): Promise<any> {
const user = await this.authService.validateUser(profile);
return user;
}
}
Check this article: OAuth2 in NestJS for Social Login (Google, Facebook, Twitter, etc) and this repo: https://github.com/thisismydesign/nestjs-starter
In the validate method of LinkedinStrategy you need to find or create the user (probably store in your DB), e.g.:
export class LinkedinStrategy extends PassportStrategy(Strategy) {
// constructor...
async validate(accessToken, refreshToken, profile) {
let user = await this.usersService.findOneByProvider('linkedin', profile.id);
if (!user) {
user = await this.usersService.create({
provider: 'linkedin',
providerId: id,
name: profile.name,
username: profile.email,
});
}
return user;
}
}
In the controller's callback endpoint you can issue a JWT token to handle the User's session within the app:
#Get('auth/linkedin/callback')
#UseGuards(AuthGuard('linkedin'))
linkedinCallBack(#Req() req) {
const { accessToken } = this.jwtAuthService.login(req.user);
res.cookie('jwt', accessToken);
return res.redirect('/profile');
}
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 would like to display a small loading logo while the firebase authentication is retrieving a user token, before starting "for real" the single page application.
So far I have an authentication service :
constructor(
public afAuth: AngularFireAuth,
) {
this.afAuth.onAuthStateChanged(user => {
if (user) {
this.setCredentials(user)
}
})
}
setCredentials(user: firebase.User) {
return user.getIdTokenResult(true).then(idTokenResult => {
this.credentials = {
userId: idTokenResult.claims.id,
role: idTokenResult.claims.role,
token: idTokenResult.token,
};
// STARTS THE APPLICATION NOW ?
})
}
Is it possible to achieve such behavior ? I've read about APP_INITIALIZER without success. I want to avoid localstorage / session storage and instead rely solely on this initialization.
Update :
created an init function :
export function initApp(auth: AuthService, afAuth: AngularFireAuth) {
return () => {
return new Promise((resolve) => {
afAuth.user.pipe(
take(1),
).subscribe(user => {
if (user) {
auth.setCredentials(user)
.then(() => resolve())
} else {
resolve();
}
})
});
}
}
And edited AppModule providers:
providers: [
interceptorProviders /* my interceptors */,
{
provide: APP_INITIALIZER,
useFactory: initApp,
deps: [AuthService, AngularFireAuth],
multi: true
}
]
Still need to figure out how to add a waiting logo but it's another question. I'll update asap.
Answering to my own question
To summarize I wanted to make sure my token claims (role, and user id per say) associated with a firebase user were stored in my auth service before dealing with routing, because components inside these routes would use those credentials.
In the end I did not follow the APP_INITIALIZER that is not really a good solution.
Auth Service
private _credentials: BehaviorSubject<Credentials> = new BehaviorSubject<Credentials>(null);
public readonly credentials$: Observable<Credentials> = this._credentials.asObservable();
constructor(private afAuth: AngularFireAuth) {
this.afAuth.authState.subscribe(user => {
this._credentials.next(null);
if (user) {
user.getIdTokenResult().then(data => {
const credentials = {
role: data.claims.role,
token: data.token,
userId: data.claims.userId
}
this._credentials.next(credentials);
console.log(credentials);
})
} else {
this._credentials.next({role: null, token: null, userId: null});
}
})
}
get credentials(): Credentials {
return this._credentials.value;
}
Display a waiting spinner in app.component
Below prevents routes from displaying if credentials not set.
In the template :
<div *ngIf="!(credentials$ | async)" class="logged-wrapper">
<div class="spinner-wrapper">
<mat-spinner class="spinner"></mat-spinner>
</div>
</div>
<router-outlet *ngIf="(credentials$ | async)"></router-outlet>
In the component :
credentials$: Observable<any>;
constructor(
private auth: AuthService,
) {
this.credentials$ = this.auth.credentials$;
}
Auth Guard
The takewhile allows me to make sure my credentials are set before going further.
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot):Promise<boolean> {
return new Promise((resolve) => {
this.auth.credentials$.pipe(
takeWhile(credentials => credentials === null),
).subscribe({
complete: () => {
const credentials = this.auth.credentials
if (!credentials.role) {
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } })
resolve(false);
}
if (next.data.roles && next.data.roles.indexOf(credentials.role) === -1) {
this.router.navigate(['/']);
resolve(false);
}
resolve(true)
}
})
})
}
You should use your authentication service in a CanActivate router guard: https://angular.io/api/router/CanActivate
This means your AppModule will initially load and then your child route (ex. MainModule with router path '') has the guard. Then in AppModule you can check for the status of the service and show a loading information until MainModule is activated (when firebase auth is finished)
I can't get user from request in decorator nest, pleas help me.
Middleware good working it find user by token and save user in request
my middleware:
import { Injectable, NestMiddleware, HttpStatus } from '#nestjs/common';
import { HttpException } from '#nestjs/common/exceptions/http.exception';
import { Request, Response } from 'express';
import { AuthenticationService } from '../modules/authentication-v1/authentication.service';
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor(
private readonly authenticationService : AuthenticationService
) {
}
async use(req: Request, res: Response, next: Function) {
let token = req.headers;
if(!token) {
throw new HttpException('token is required', 401);
}
if (!token.match(/Bearer\s(\S+)/)) {
throw new HttpException('Unsupported token', 401);
}
const [ tokenType, tokenValue ] = token.split(' ');
try {
const result = await this.authenticationService.getAccessToken(tokenValue);
req.user = result;
next();
} catch (e) {
throw new HttpException(e.message, 401);
}
}
}
but here request don't have property user and i don't know why
user decorator:
export const User = createParamDecorator((data: any, req) => {
return req.user; // but here user undefined
});
app module:
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthenticationMiddleware)
.forRoutes({ path: 'auto-reports-v1', method: RequestMethod.GET });
}
}
route method:
#UseInterceptors(LoggingInterceptor)
#Controller('auto-reports-v1')
#ApiTags('auto-reports-v1')
export class AutoReportsController {
constructor(private readonly autoReportsService: AutoReportsService) {}
#Get()
async findAll(
#Query() filter: any,
#User() user: any): Promise<Paginated> {
return this.autoReportsService.findPaginatedByFilter(filter, user);
}
}
In NestJS with Fastify, middleware attaches values to req.raw. This is because middleware runs before the request gets wrapped by the FastifyRequest object, so all value attachments are made to the IncomingRequest object (same as Express Request object). Fastify will then wrap the IncomingRequest in its own FastifyRequest object and expose the IncomingRequest through req.raw meaning the user you are looking for is at req.raw.user not req.user. If you want to have the same functionality across Express and Fastify, I'd suggest using a Guard instead.
Accepted answer didn't help me, but the "custom decorators" from nest js documentation uses createParamDecorator with ExecutionContext as second parameter, and this worked for me:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
i find weird things. I have AuthService which saves authentication needs of my apps, included authentication token.
#IonicPage()
#Component({
selector: 'page-login',
templateUrl: 'login.html',
})
export class LoginPage {
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl:ModalController,public auth: AuthService) {
}
ionViewDidLoad() {
console.log(this.auth)
console.log(this.auth.loggedIn)
if(this.auth.loggedIn){
console.log(this.auth);
this.navCtrl.push("TabsPage");
}
}
}
when i call
console.log(this.auth)
it returned authentication
buth when i call
console.log(this.auth.loggedIn)
it return null
this my auth.service.ts
import { Injectable, NgZone, Component } from '#angular/core';
import { Storage } from '#ionic/storage';
// Import AUTH_CONFIG, Auth0Cordova, and auth0.js
import { AUTH_CONFIG } from './auth.config';
import Auth0Cordova from '#auth0/cordova';
import * as auth0 from 'auth0-js';
#Injectable()
export class AuthService {
Auth0 = new auth0.WebAuth(AUTH_CONFIG);
Client = new Auth0Cordova(AUTH_CONFIG);
accessToken: string;
user: any;
loggedIn: boolean;
loading = true;
constructor(
public zone: NgZone,
private storage: Storage
) {
this.storage.get('profile').then(user => this.user = user);
this.storage.get('access_token').then(token => this.accessToken = token);
this.storage.get('expires_at').then(exp => {
this.loggedIn = Date.now() < JSON.parse(exp);
this.loading = false;
});
}
login() {
this.loading = true;
const options = {
scope: 'openid profile offline_access'
};
// Authorize login request with Auth0: open login page and get auth results
this.Client.authorize(options, (err, authResult) => {
if (err) {
throw err;
}
// Set access token
this.storage.set('access_token', authResult.accessToken);
this.accessToken = authResult.accessToken;
// Set access token expiration
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
this.storage.set('expires_at', expiresAt);
// Set logged in
this.loading = false;
this.loggedIn = true;
// Fetch user's profile info
this.Auth0.client.userInfo(this.accessToken, (err, profile) => {
if (err) {
throw err;
}
this.storage.set('profile', profile).then(val =>
this.zone.run(() => this.user = profile)
);
});
});
}
logout() {
this.storage.remove('profile');
this.storage.remove('access_token');
this.storage.remove('expires_at');
this.accessToken = null;
this.user = null;
this.loggedIn = false;
}
isLoggedIn() :boolean{
return this.loggedIn;
}
}
i'm using ionic3 and auth0 authentication, previously i think that was my fault to not use public identifier on my property. but when i change the property to public, or create getter method that returned the property it still not working at all.
This is due to when the chrome console evaluates the object. If you open the object in your console, you'll see a tiny blue info icon. This will say:
Value was evaluated just now
Basically what happens is that the object content changed between the time you logged it, and the time you opened it in your console.
The login action is asynchronous, which means that the loggedIn property on the auth object will be set after the ionViewDidLoad is called. Perhaps a good thing would be to set the auth inside an APP_INITIALIZER provider, or have some Observable on your auth on which you can listen for auth changes
1.you have colling this.loggedIn before assign so that it's undefined.
in your case write console.log(this.auth.loggedIn) after login. please check that scenario.
2.for now, assign some value for loggedIn variable then print it will print a value
loggedIn: boolean; => loggedIn: boolean=false;
then print a value it will work
in some other component
ngOnInit() {
console.log(this.auth.loggedIn)
}
I'm trying to create a user token based on the secret of the user trying to log in. However instead of using a secret from the environment I want to use a secret assigned to a user object inside the database.
import { Injectable } from '#nestjs/common';
import { JwtService } from '#nestjs/jwt';
import { UserService } from '#src/modules/user/services';
#Injectable()
export class AuthService {
public constructor(private readonly jwtService: JwtService,
private readonly userService: UserService) {}
public async createToken(email: string): Promise<JwtReply> {
const expiresIn = 60 * 60 * 24;
const user = await this.userService.user({ where: { email } });
const accessToken = await this.jwtService.signAsync({ email: user.email },
/* user.secret ,*/
{ expiresIn });
return {
accessToken,
expiresIn,
};
}
}
I'm new to Nestjs and maybe I'm missing something.
node-jsonwebtoken does provide the necessary parameter in the sign(...) function. nestjs/jwt is missing this parameter (see code). How would you solve it without using node-jsonwebtoken or maybe a more abstract question: does my way of handling user secret make sense here? Thanks.
This is not yet possible solely with nest's JwtModule but you can easily implement the missing parts yourself.
Live Demo
You can create tokens by calling the following routes:
user1 (secret: '123'): https://yw7wz99zv1.sse.codesandbox.io/login/1
user2 (secret: '456'): https://yw7wz99zv1.sse.codesandbox.io/login/2
Then call the protected route '/' with your token and receive your user:
curl -X GET https://yw7wz99zv1.sse.codesandbox.io/ \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxIiwiaWF0IjoxNTUzNjQwMjc5fQ.E5o3djesqWVHNGe-Hi3KODp0aTiQU9X_H3Murht1R5U'
How does it work?
In the AuthService I'm just using the standard jsonwebtoken library to create the token. You can then call createToken from your login route:
import * as jwt from 'jsonwebtoken';
export class AuthService {
constructor(private readonly userService: UserService) {}
createToken(userId: string) {
const user = this.userService.getUser(userId);
return jwt.sign({ userId: user.userId }, user.secret, { expiresIn: 3600 });
}
// ...
}
In the JwtStrategy you use secretOrKeyProvider instead of secretOrKey which can asynchronously access the UserService to get the user secret dynamically:
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKeyProvider: (request, jwtToken, done) => {
const decodedToken: any = jwt.decode(jwtToken);
const user = this.userService.getUser(decodedToken.userId);
done(null, user.secret);
},
});
}
// ...
}
Note that the options you pass to the JwtModule like expiresIn will not be used, instead directly pass your options in the AuthService. Import the JwtModule without any options:
JwtModule.register({})
General
Does my way of handling user secret make sense here?
This is hard to answer without knowing your exact requirements. I guess there are use cases for jwt with dynamic secrets but with it you are losing a great property of jwt: they are stateless. This means that your AuthService can issue a jwt token and some ProductService that requires authentication can just trust the jwt (it knows the secret) without making any calls to other services (i.e. UserService which has to query the database).
If user-related keys are not a hard requirement consider rotating the keys frequently instead by making use of jwt's kid property.
The option to add secret into JwtSignOptions has been added in nestjs/jwt version 7.1.0.
With that, the example would be:
public async createToken(email: string): Promise<JwtReply> {
const expiresIn = 60 * 60 * 24;
const user = await this.userService.user({ where: { email } });
const accessToken = await this.jwtService.signAsync(
{ email: user.email },
{ expiresIn,
secret: user.secret,
});
return {
accessToken,
expiresIn,
};
}
I had also case to sign access and refresh tokens with different secret keys.
If u follow nestjs docs, u see JwtModule is registered with single config and token is signed without options (with default config). To use jwtService sign function with options import JwtModule.register with empty object
import { JwtModule } from '#nestjs/jwt';
#Module({
imports: [JwtModule.register({})],
providers: [],
controllers: []
})
export class AuthModule {}
And making config file with different sign options
#Injectable()
export class ApiConfigService {
constructor(private configService: ConfigService) {
}
get accessTokenConfig(): any {
return {
secret: this.configService.get('JWT_ACCESS_TOKEN_KEY'),
expiresIn: eval(this.configService.get('JWT_ACCESS_TOKEN_LIFETIME'))
}
}
get refreshTokenConfig(): any {
return {
secret: this.configService.get('JWT_REFRESH_TOKEN_KEY'),
expiresIn: eval(this.configService.get('JWT_REFRESH_TOKEN_LIFETIME'))
}
}
}
u may sign token with desired config
#Injectable()
export class AuthService {
constructor(private jwtService: JwtService, private apiConfigService: ApiConfigService ) {}
login(user: any) {
let payload = {username: user.username, id: user.id};
let jwt = this.jwtService.sign(payload, this.apiConfigService.accessTokenConfig);
//
return { token: jwt };
}
}