Last active
August 23, 2020 10:17
-
-
Save paztek/5249e9cb7151fd0b9abf12546e6aa053 to your computer and use it in GitHub Desktop.
nestjs-authentication-example-2
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'; | |
import { AUTHENTICATION_STRATEGY_TOKEN } from './authentication.strategy'; | |
import { KeycloakAuthenticationStrategy } from './strategy/keycloak.strategy'; | |
import { FakeAuthenticationStrategy } from './strategy/fake.strategy'; | |
@Module({ | |
imports: [ | |
HttpModule, | |
], | |
providers: [ | |
AuthenticationGuard, | |
AuthenticationService, | |
{ | |
provide: AUTHENTICATION_STRATEGY_TOKEN, | |
useClass: process.env.NODE_ENV === 'test' ? FakeAuthenticationStrategy : KeycloakAuthenticationStrategy, | |
}, | |
], | |
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 { Inject, Injectable, Logger } from '@nestjs/common'; | |
import { User } from './user.model'; | |
import { AUTHENTICATION_STRATEGY_TOKEN, AuthenticationStrategy } from './authentication.strategy'; | |
export class AuthenticationError extends Error {} | |
@Injectable() | |
export class AuthenticationService { | |
private logger = new Logger(AuthenticationService.name); | |
constructor( | |
@Inject(AUTHENTICATION_STRATEGY_TOKEN) private readonly strategy: AuthenticationStrategy, | |
) {} | |
async authenticate(accessToken: string): Promise<User> { | |
try { | |
const userInfos = await this.strategy.authenticate(accessToken); | |
const user = { | |
id: userInfos.sub, | |
username: userInfos.preferred_username, | |
}; | |
/** | |
* Perform any addition business logic with the user: | |
* - insert user in "users" table on first authentication, | |
* - etc. | |
*/ | |
return user; | |
} catch (e) { | |
this.logger.error(e.message, e.stackTrace); | |
throw new AuthenticationError(e.message); | |
} | |
} | |
} |
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
export const AUTHENTICATION_STRATEGY_TOKEN = 'AuthenticationStrategy'; | |
export interface KeycloakUserInfoResponse { | |
sub: string; | |
email_verified: boolean; | |
name: string; | |
preferred_username: string; | |
given_name: string; | |
family_name: string, | |
email: string; | |
} | |
export interface AuthenticationStrategy { | |
authenticate(accessToken: string): Promise<KeycloakUserInfoResponse>; | |
} |
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 * as jwt from 'jsonwebtoken'; | |
import { v4 as uuid } from 'uuid'; | |
import { KeycloakUserInfoResponse } from '../../../src/authentication/authentication.strategy'; | |
export function createToken(id: string = uuid(), username = 'john.doe'): string { | |
const userInfos: KeycloakUserInfoResponse = { | |
sub: id, | |
email: `${username}@example.com`, | |
email_verified: true, | |
name: 'John doe', | |
preferred_username: username, | |
given_name: 'John', | |
family_name: 'Doe', | |
}; | |
return jwt.sign(userInfos, 'secret'); | |
} |
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 { Test } from '@nestjs/testing'; | |
import * as request from 'supertest'; | |
import { AppModule } from './app.module'; | |
import { INestApplication } from '@nestjs/common'; | |
import { createToken } from '../test/helpers/authentication/create-token'; | |
describe('Hello E2E', () => { | |
let app: INestApplication; | |
let server: any; | |
const token = createToken(); | |
beforeEach(async () => { | |
const module = await Test.createTestingModule({ | |
imports: [ | |
AppModule | |
], | |
}).compile(); | |
app = module.createNestApplication(); | |
await app.init(); | |
server = app.getHttpServer(); | |
}); | |
describe('GET /', () => { | |
it('requires authentication', async () => { | |
const response = await request(server) | |
.get('/'); | |
expect(response.status).toEqual(401); | |
}); | |
it('returns "Hello World!"', async () => { | |
const response = await request(server) | |
.get('/') | |
.set('Authorization', `Bearer ${token}`); | |
expect(response.status).toEqual(200); | |
}); | |
}); | |
}); |
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 { Injectable } from '@nestjs/common'; | |
import * as jwt from 'jsonwebtoken'; | |
import { AuthenticationStrategy, KeycloakUserInfoResponse } from '../authentication.strategy'; | |
@Injectable() | |
export class FakeAuthenticationStrategy implements AuthenticationStrategy { | |
/** | |
* Blindly trust the JWT, assume it has the Keycloak structure and return the decoded payload | |
*/ | |
public authenticate(accessToken: string): Promise<KeycloakUserInfoResponse> { | |
return Promise.resolve(jwt.decode(accessToken)); | |
} | |
} |
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 { AuthenticationStrategy, KeycloakUserInfoResponse } from '../authentication.strategy'; | |
@Injectable() | |
export class KeycloakAuthenticationStrategy implements AuthenticationStrategy { | |
private readonly baseURL: string; | |
private readonly realm: string; | |
constructor( | |
private readonly 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<KeycloakUserInfoResponse> { | |
const url = `${this.baseURL}/realms/${this.realm}/protocol/openid-connect/userinfo`; | |
const response = await this.httpService.get<KeycloakUserInfoResponse>(url, { | |
headers: { | |
authorization: `Bearer ${accessToken}`, | |
}, | |
}).toPromise(); | |
return response.data; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment