Last active
August 23, 2020 09:06
-
-
Save paztek/fff6e7ccf2055e413be704eb4ddb7ac5 to your computer and use it in GitHub Desktop.
nestjs-authentication-example-1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Module } from '@nestjs/common'; | |
import { AppController } from './app.controller'; | |
import { AppService } from './app.service'; | |
import { AuthenticationModule } from './authentication/authentication.module'; | |
@Module({ | |
imports: [ | |
AuthenticationModule, | |
], | |
controllers: [ | |
AppController, | |
], | |
providers: [ | |
AppService, | |
], | |
}) | |
export class AppModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { CanActivate, ExecutionContext, HttpException, HttpStatus, Injectable } from '@nestjs/common'; | |
import { AuthenticationService } from './authentication.service'; | |
import { Request } from 'express'; | |
@Injectable() | |
export class AuthenticationGuard implements CanActivate { | |
constructor( | |
private readonly authenticationService: AuthenticationService, | |
) {} | |
async canActivate(context: ExecutionContext): Promise<boolean> { | |
const request: Request = context.switchToHttp().getRequest(); | |
const header = request.header('Authorization'); | |
if (!header) { | |
throw new HttpException('Authorization: Bearer <token> header missing', HttpStatus.UNAUTHORIZED); | |
} | |
const parts = header.split(' '); | |
if (parts.length !== 2 || parts[0] !== 'Bearer') { | |
throw new HttpException('Authorization: Bearer <token> header invalid', HttpStatus.UNAUTHORIZED); | |
} | |
const token = parts[1]; | |
try { | |
// Store the user on the request object if we want to retrieve it from the controllers | |
request['user'] = await this.authenticationService.authenticate(token); | |
return true; | |
} catch (e) { | |
throw new HttpException(e.message, HttpStatus.UNAUTHORIZED); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { HttpModule, Module } from '@nestjs/common'; | |
import { AuthenticationGuard } from './authentication.guard'; | |
import { AuthenticationService } from './authentication.service'; | |
@Module({ | |
imports: [ | |
HttpModule, | |
], | |
providers: [ | |
AuthenticationGuard, | |
AuthenticationService, | |
], | |
exports: [ | |
AuthenticationService, | |
], | |
}) | |
export class AuthenticationModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { HttpService, Injectable } from '@nestjs/common'; | |
import { User } from './user.model'; | |
interface KeycloakUserInfoResponse { | |
sub: string; | |
email_verified: boolean; | |
name:string; | |
preferred_username: string; | |
given_name: string; | |
family_name: string, | |
email: string; | |
} | |
export class AuthenticationError extends Error {} | |
@Injectable() | |
export class AuthenticationService { | |
private readonly baseURL: string; | |
private readonly realm: string; | |
constructor( | |
private httpService: HttpService, | |
) { | |
this.baseURL = process.env.KEYCLOAK_BASE_URL; | |
this.realm = process.env.KEYCLOAK_REALM; | |
} | |
/** | |
* Call the OpenId Connect UserInfo endpoint on Keycloak: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo | |
* | |
* If it succeeds, the token is valid and we get the user infos in the response | |
* If it fails, the token is invalid or expired | |
*/ | |
async authenticate(accessToken: string): Promise<User> { | |
const url = `${this.baseURL}/realms/${this.realm}/protocol/openid-connect/userinfo`; | |
try { | |
const response = await this.httpService.get<KeycloakUserInfoResponse>(url, { | |
headers: { | |
authorization: `Bearer ${accessToken}`, | |
}, | |
}).toPromise(); | |
return { | |
id: response.data.sub, | |
username: response.data.preferred_username, | |
}; | |
} catch (e) { | |
throw new AuthenticationError(e.message); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment