oidc-provider module for Nest framework (node.js)
$ npm i --save nest-oidc-provider oidc-provider
OR
$ yarn add nest-oidc-provider oidc-provider
OR
$ pnpm add nest-oidc-provider oidc-provider
⚠️ Version 8 of
oidc-provider
is now ESM-only, which is not yet supported by NestJS natively (nest#7021, nest#8736). This library enables the use of the ESM-only version ofoidc-provider
for Node.js <= 20.17.x via dynamic imports. To avoid errors like [ERR_REQUIRE_ESM], all interfaces should be imported from this package, and the module should be accessed through dependency injection. Use@InjectOidcModule()
to inject theoidc-provider
module and@InjectOidcProvider()
for the running instance. You must not import anything directly fromoidc-provider
, unless you're using Node.js >= 20.17.x with the experimental--experimental-require-module
flag (#54447)!
You need to install the oidc-provider
@types package if you want to use the re-exported types from this library.
npm install @types/oidc-provider --save-dev
@Module({
imports: [
OidcModule.forRoot({
issuer: 'http://localhost:3000',
path: '/oidc',
oidc: ... // oidc-provider configuration
})
],
})
export class AppModule {}
You can pass a factory
function to customize the provider instantiation.
@Module({
imports: [
OidcModule.forRoot({
issuer: 'http://localhost:3000',
path: '/oidc',
factory: ({ issuer, config, module }) => {
// `module` is the import from `oidc-provider`
const provider = new module.Provider(issuer, config);
provider.on('server_error', (ctx, err) => {...})
return provider;
},
oidc: ... // oidc-provider configuration
})
],
})
export class AppModule {}
You can set the proxy
option to true
to trust TLS offloading proxies.\
For more info visit the oidc-provider
documentation: Trusting TLS offloading proxies
@Module({
imports: [
OidcModule.forRoot({
issuer: 'http://localhost:3000',
path: '/oidc',
proxy: true, // <= trust TLS offloading proxies
oidc: {...}
})
],
})
export class AppModule {}
useFactory
@Module({
imports: [
OidcModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
issuer: configService.get<string>('ISSUER'),
path: configService.get<string>('OIDC_PATH'),
oidc: ... // oidc-provider configuration
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
useClass
@Module({
imports: [
OidcModule.forRootAsync({
useClass: OidcConfigService,
}),
],
})
export class AppModule {}
Note that in this example, the OidcConfigService
has to implement the OidcModuleOptionsFactory
interface, as shown below.
import type { OidcModuleOptionsFactory } from 'nest-oidc-provider';
@Injectable()
export class OidcConfigService implements OidcModuleOptionsFactory {
constructor(private readonly @InjectConnection() conn: Connection) {}
createModuleOptions(): OidcModuleOptions {
return {
issuer: 'http://localhost:3001',
path: '/oidc',
oidc: ..., // oidc-provider configuration
};
}
createAdapterFactory?(): AdapterFactory {
return (modelName: string) => new MyAdapter(modelName, this.conn);
}
}
You can omit the Adapter
option of oidc-provider configuration if you implement the createAdapterFactory
method.
useExisting
@Module({
imports: [
OidcModule.forRootAsync({
imports: [OidcConfigModule],
useExisting: OidcConfigService,
}),
],
})
export class AppModule {}
To be able to access the exports of the oidc-provider
module or the running instance, you need to use decorators or injection tokens:
import {
InjectOidcModule,
InjectOidcProvider,
type Provider,
type ProviderModule,
} from 'nest-oidc-provider';
@Controller('/some-controller')
export class SomeController {
constructor(
/** Returns exports from the `oidc-provider` module */
@InjectOidcModule() oidc: ProviderModule,
/** Returns the running `oidc-provider` instance */
@InjectOidcProvider() provider: Provider,
) {}
}
OR
import {
OIDC_PROVIDER,
OIDC_PROVIDER_MODULE,
type Provider,
type ProviderModule,
} from 'nest-oidc-provider';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const { Provider, errors, interactionPolicy } =
app.get<ProviderModule>(OIDC_PROVIDER_MODULE);
const provider = app.get<Provider>(OIDC_PROVIDER);
await app.listen(3000);
}
@OidcInteraction()
Returns an instance of InteractionHelper
class.
import { OidcInteraction, type InteractionHelper } from 'nest-oidc-provider';
@Get(':uid')
@Render('login')
async login(
@OidcInteraction() interaction: InteractionHelper
) {
const { prompt, params, uid } = await interaction.details();
const client = await this.provider.Client.find(params.client_id as string);
return { prompt, client, params, uid, ...};
}
The InteractionHelper
class is just a helper that omits the req
and res
parameters from the existing interaction methods in oidc-provider
.
interface InteractionHelper {
details(): Promise<InteractionDetails>;
finished(
result: InteractionResults,
options?: { mergeWithLastSubmission?: boolean },
): Promise<void>;
result(
result: InteractionResults,
options?: { mergeWithLastSubmission?: boolean },
): Promise<string>;
}
@OidcContext()
Returns an instance of KoaContextWithOIDC
.
import { OidcContext, type KoaContextWithOIDC } from 'nest-oidc-provider';
@Get()
async index(@OidcContext() ctx: KoaContextWithOIDC) {
const { oidc: { provider } } = ctx;
const session = await provider.Session.get(ctx);
//...
}
@OidcSession()
Returns the user Session
from the current context, equivalent to retrieving the session from KoaContextWithOIDC
.
import { OidcSession, type Session } from 'nest-oidc-provider';
@Get()
async index(@OidcSession() session: Session) {
//...
}
A complete example can be found in the example directory.
You are welcome to contribute to this project, just open a PR.
See CHANGELOG for more information.
This project is MIT licensed.