-
-
Save ferrerojosh/653500646a0debf01548d315fe61a773 to your computer and use it in GitHub Desktop.
import { FactoryProvider, Logger } from '@nestjs/common'; | |
import Keycloak from 'keycloak-connect'; | |
export const KEYCLOAK_INSTANCE = 'KEYCLOAK_INSTANCE'; | |
export const keycloakProvider: FactoryProvider = { | |
provide: KEYCLOAK_INSTANCE, | |
useFactory: () => { | |
const keycloakConfig: any = { | |
realm: '', | |
clientId: '', | |
secret: '', | |
authServerUrl: '', | |
}; | |
const keycloak: any = new Keycloak({}, keycloakConfig); | |
// We just need it to return next(false) so our guard will be able to latch on | |
keycloak.accessDenied = (req, res, next) => { | |
next(false); | |
}; | |
return keycloak; | |
} | |
}; |
import { SetMetadata } from '@nestjs/common'; | |
export const Permissions = (...permissions: string[]) => | |
SetMetadata('permissions', permissions); |
import { | |
CanActivate, | |
ExecutionContext, | |
Inject, | |
Injectable, | |
Logger, | |
} from '@nestjs/common'; | |
import { Reflector } from '@nestjs/core'; | |
import KeycloakConnect from 'keycloak-connect'; | |
import { KEYCLOAK_INSTANCE } from './keycloak.provider'; | |
@Injectable() | |
export class ResourceGuard implements CanActivate { | |
constructor( | |
@Inject(KEYCLOAK_INSTANCE) | |
private keycloak: KeycloakConnect.Keycloak, | |
private readonly reflector: Reflector, | |
) {} | |
async canActivate(context: ExecutionContext): Promise<boolean> { | |
const permissions = this.reflector.get<string[]>( | |
'permissions', | |
context.getHandler(), | |
); | |
const [request, response] = [ | |
this.getRequest(context), | |
context.switchToHttp().getResponse(), | |
]; | |
const enforcerFn = createEnforcerContext(request, response); | |
return await enforcerFn(this.keycloak, permissions); | |
} | |
getRequest<T = any>(context: ExecutionContext): T { | |
return context.switchToHttp().getRequest(); | |
} | |
} | |
const createEnforcerContext = ( | |
request, | |
response, | |
) => (keycloak: KeycloakConnect.Keycloak, permissions: string[]) => | |
new Promise<boolean>((resolve, reject) => | |
keycloak.enforcer(permissions)(request, response, next => { | |
if (next !== undefined) { | |
resolve(false); | |
} else { | |
resolve(true); | |
} | |
}), | |
); |
That config works without provide a session store? I'm using keycloak-connect on a nest project too.
It works fine if you're only using it for REST APIs. You don't necessarily need session-store if you're not serving html pages.
It works fine if you're only using it for REST APIs. You don't necessarily need session-store if you're not serving html pages.
This is exactly my case. I have an Angular client as static content of my Nest application.
I saw something about using the passport-js strategy with Nest and Keycloak PKCE, but I can't find a tutorial using Nest.
This library is referenced by keycloak docs, but I had no success in trying to implement it with Nest:https://github.com/exlinc/keycloak-passport
I'm considering to use an HTTP server such as NgInx and proxy all request with /api to the Nest app, then i will separate backend and frontend on 2 diffrent apps. Probaly on that way your strategy will work on my project.
Thanks.
This is exactly my case. I have an Angular client as static content of my Nest application.
I saw something about using the passport-js strategy with Nest and Keycloak PKCE, but I can't find a tutorial using Nest.
This library is referenced by keycloak docs, but I had no success in trying to implement it with Nest:https://github.com/exlinc/keycloak-passport
I'm considering to use an HTTP server such as NgInx and proxy all request with /api to the Nest app, then i will separate backend and frontend on 2 diffrent apps. Probaly on that way your strategy will work on my project.
Thanks.
Passport is an authentication middleware, this gist if for keycloak authorization, they are two different things.
If you are using Keycloak and want to use the @nestjs/passport
authentication guard, you can use the following package: https://www.npmjs.com/package/passport-keycloak-bearer
And implement the passport strategy:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import KeycloakBearerStrategy from 'passport-keycloak-bearer';
@Injectable()
export class KeycloakStrategy extends PassportStrategy(KeycloakBearerStrategy) {
constructor() {
super({
realm: '',
url: '',
});
}
async validate(jwtPayload: any): Promise<any> {
return jwtPayload;
}
}
If you want to use the library you referenced, you could also do that:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import KeycloakStrategy from '@exlinc/keycloak-passport';
@Injectable()
export class KeycloakStrategy extends PassportStrategy(KeycloakStrategy) {
constructor() {
super({
host: process.env.KEYCLOAK_HOST,
realm: process.env.KEYCLOAK_REALM,
clientID: process.env.KEYCLOAK_CLIENT_ID,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
callbackURL: `/api${AUTH_KEYCLOAK_CALLBACK}`
});
}
async validate(accessToken, refreshToken, profile): Promise<any> {
return profile;
}
}
Then just call AuthGuard('keycloak')
in Nest and you're good to go.
P.S I use passport-keycloak-bearer
for authentication in my Nest app but not for keycloak resource authorization which this gist is about.
I saw something about using the passport-js strategy with Nest and Keycloak PKCE, but I can't find a tutorial using Nest.
You should be using keycloak-js in your Angular application (I use it too) if you want PKCE flow.
Also the Angular and Nest app should be two different clients in Keycloak, they can't be the same.
@ferrerojosh did you make it work?
@ferrerojosh did you make it work?
I made a library for it.
@ferrerojosh does it work with GraphQL?
@ferrerojosh does it work with GraphQL?
It should, some guy made a PR for it.
That config works without provide a session store? I'm using keycloak-connect on a nest project too.