@dismissible/nestjs-jwt-auth-hook 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # @dismissible/nestjs-jwt-auth-hook
2
+
3
+ JWT authentication hook for Dismissible applications using OpenID Connect (OIDC) well-known discovery.
4
+
5
+ ## Overview
6
+
7
+ This library provides a lifecycle hook that integrates with the `@dismissible/nestjs-dismissible` module to authenticate requests using JWT bearer tokens. It validates tokens using JWKS (JSON Web Key Set) fetched from an OIDC well-known endpoint.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @dismissible/nestjs-jwt-auth-hook @nestjs/axios axios
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Basic Setup
18
+
19
+ ```typescript
20
+ import { Module } from '@nestjs/common';
21
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
22
+ import { JwtAuthHookModule, JwtAuthHook } from '@dismissible/nestjs-jwt-auth-hook';
23
+
24
+ @Module({
25
+ imports: [
26
+ // Configure the JWT auth hook module
27
+ JwtAuthHookModule.forRoot({
28
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
29
+ issuer: 'https://auth.example.com',
30
+ audience: 'my-api',
31
+ }),
32
+
33
+ // Pass the hook to the DismissibleModule
34
+ DismissibleModule.forRoot({
35
+ hooks: [JwtAuthHook],
36
+ // ... other options
37
+ }),
38
+ ],
39
+ })
40
+ export class AppModule {}
41
+ ```
42
+
43
+ ### Async Configuration
44
+
45
+ When configuration values come from environment variables or other async sources:
46
+
47
+ ```typescript
48
+ import { Module } from '@nestjs/common';
49
+ import { ConfigService } from '@nestjs/config';
50
+ import { DismissibleModule } from '@dismissible/nestjs-dismissible';
51
+ import { JwtAuthHookModule, JwtAuthHook } from '@dismissible/nestjs-jwt-auth-hook';
52
+
53
+ @Module({
54
+ imports: [
55
+ JwtAuthHookModule.forRootAsync({
56
+ useFactory: (configService: ConfigService) => ({
57
+ wellKnownUrl: configService.getOrThrow('OIDC_WELL_KNOWN_URL'),
58
+ issuer: configService.get('OIDC_ISSUER'),
59
+ audience: configService.get('OIDC_AUDIENCE'),
60
+ }),
61
+ inject: [ConfigService],
62
+ }),
63
+
64
+ DismissibleModule.forRoot({
65
+ hooks: [JwtAuthHook],
66
+ }),
67
+ ],
68
+ })
69
+ export class AppModule {}
70
+ ```
71
+
72
+ ## Configuration Options
73
+
74
+ | Option | Type | Required | Default | Description |
75
+ | ------------------- | ---------- | -------- | ----------- | ------------------------------------------------------------------------------------------- |
76
+ | `enabled` | `boolean` | Yes | `true` | Whether JWT authentication is enabled |
77
+ | `wellKnownUrl` | `string` | Yes\* | - | The OIDC well-known URL (e.g., `https://auth.example.com/.well-known/openid-configuration`) |
78
+ | `issuer` | `string` | No | - | Expected issuer (`iss`) claim. If not provided, issuer validation is skipped. |
79
+ | `audience` | `string` | No | - | Expected audience (`aud`) claim. If not provided, audience validation is skipped. |
80
+ | `algorithms` | `string[]` | No | `['RS256']` | Allowed algorithms for JWT verification |
81
+ | `jwksCacheDuration` | `number` | No | `600000` | JWKS cache duration in milliseconds (10 minutes) |
82
+ | `requestTimeout` | `number` | No | `30000` | Request timeout in milliseconds (30 seconds) |
83
+ | `priority` | `number` | No | `-100` | Hook priority (lower numbers run first) |
84
+
85
+ \* `wellKnownUrl` is only required when `enabled` is `true`.
86
+
87
+ ## Environment Variables
88
+
89
+ When using the Dismissible API Docker image or the standalone API, these environment variables configure JWT authentication:
90
+
91
+ | Variable | Description | Default |
92
+ | ------------------------------------------ | -------------------------------------- | -------- |
93
+ | `DISMISSIBLE_JWT_AUTH_ENABLED` | Enable JWT authentication | `true` |
94
+ | `DISMISSIBLE_JWT_AUTH_WELL_KNOWN_URL` | OIDC well-known URL for JWKS discovery | `""` |
95
+ | `DISMISSIBLE_JWT_AUTH_ISSUER` | Expected issuer claim (optional) | `""` |
96
+ | `DISMISSIBLE_JWT_AUTH_AUDIENCE` | Expected audience claim (optional) | `""` |
97
+ | `DISMISSIBLE_JWT_AUTH_ALGORITHMS` | Allowed algorithms (comma-separated) | `RS256` |
98
+ | `DISMISSIBLE_JWT_AUTH_JWKS_CACHE_DURATION` | JWKS cache duration in ms | `600000` |
99
+ | `DISMISSIBLE_JWT_AUTH_REQUEST_TIMEOUT` | Request timeout in ms | `30000` |
100
+ | `DISMISSIBLE_JWT_AUTH_PRIORITY` | Hook priority (lower runs first) | `-100` |
101
+
102
+ ### Example: Disabling JWT Auth for Development
103
+
104
+ ```bash
105
+ docker run -p 3001:3001 \
106
+ -e DISMISSIBLE_JWT_AUTH_ENABLED=false \
107
+ -e DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING="postgresql://..." \
108
+ dismissibleio/dismissible-api:latest
109
+ ```
110
+
111
+ ### Example: Enabling JWT Auth with Auth0
112
+
113
+ ```bash
114
+ docker run -p 3001:3001 \
115
+ -e DISMISSIBLE_JWT_AUTH_ENABLED=true \
116
+ -e DISMISSIBLE_JWT_AUTH_WELL_KNOWN_URL="https://your-tenant.auth0.com/.well-known/openid-configuration" \
117
+ -e DISMISSIBLE_JWT_AUTH_ISSUER="https://your-tenant.auth0.com/" \
118
+ -e DISMISSIBLE_JWT_AUTH_AUDIENCE="your-api-identifier" \
119
+ -e DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING="postgresql://..." \
120
+ dismissibleio/dismissible-api:latest
121
+ ```
122
+
123
+ ## How It Works
124
+
125
+ 1. **Initialization**: On module initialization, the hook fetches the OIDC configuration from the well-known URL to discover the JWKS endpoint.
126
+
127
+ 2. **Token Extraction**: For each request, the hook extracts the bearer token from the `Authorization` header.
128
+
129
+ 3. **Token Validation**: The token is validated by:
130
+ - Decoding the JWT to get the key ID (`kid`)
131
+ - Fetching the corresponding public key from JWKS
132
+ - Verifying the signature
133
+ - Validating claims (expiration, issuer, audience)
134
+
135
+ 4. **Request Handling**:
136
+ - If valid: The request proceeds
137
+ - If invalid: The request is blocked with a `403 Forbidden` response
138
+
139
+ ## Error Responses
140
+
141
+ When authentication fails, the hook returns a structured error:
142
+
143
+ ```json
144
+ {
145
+ "statusCode": 403,
146
+ "message": "Authorization failed: Token expired",
147
+ "error": "Forbidden"
148
+ }
149
+ ```
150
+
151
+ Common error messages:
152
+
153
+ - `Authorization required: Missing or invalid bearer token`
154
+ - `Authorization failed: Token expired`
155
+ - `Authorization failed: Invalid signature`
156
+ - `Authorization failed: Unable to find signing key`
157
+
158
+ ## Supported OIDC Providers
159
+
160
+ This hook works with any OIDC-compliant identity provider, including:
161
+
162
+ - Auth0
163
+ - Okta
164
+ - Keycloak
165
+ - Azure AD
166
+ - Google Identity Platform
167
+ - AWS Cognito
168
+
169
+ ## License
170
+
171
+ MIT
package/jest.config.ts ADDED
@@ -0,0 +1,27 @@
1
+ export default {
2
+ displayName: 'jwt-auth-hook',
3
+ preset: '../../jest.preset.js',
4
+ testEnvironment: 'node',
5
+ transform: {
6
+ '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.json' }],
7
+ },
8
+ moduleFileExtensions: ['ts', 'js', 'html'],
9
+ coverageDirectory: '../../coverage/libs/jwt-auth-hook',
10
+ collectCoverageFrom: [
11
+ 'src/**/*.ts',
12
+ '!src/**/*.spec.ts',
13
+ '!src/**/*.interface.ts',
14
+ '!src/**/*.dto.ts',
15
+ '!src/**/*.enum.ts',
16
+ '!src/**/index.ts',
17
+ '!src/**/*.module.ts',
18
+ ],
19
+ coverageThreshold: {
20
+ global: {
21
+ branches: 80,
22
+ functions: 80,
23
+ lines: 80,
24
+ statements: 80,
25
+ },
26
+ },
27
+ };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@dismissible/nestjs-jwt-auth-hook",
3
+ "version": "0.0.1",
4
+ "description": "JWT authentication hook for Dismissible applications using OIDC well-known discovery",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.mjs",
10
+ "require": "./src/index.js",
11
+ "types": "./src/index.d.ts"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "jwks-rsa": "^3.1.0",
16
+ "jsonwebtoken": "^9.0.0"
17
+ },
18
+ "peerDependencies": {
19
+ "@nestjs/axios": "^4.0.0",
20
+ "@nestjs/common": "^11.0.0",
21
+ "@nestjs/core": "^11.0.0",
22
+ "@dismissible/nestjs-dismissible": "*",
23
+ "@dismissible/nestjs-logger": "*"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "@nestjs/axios": {
27
+ "optional": false
28
+ },
29
+ "@nestjs/common": {
30
+ "optional": false
31
+ },
32
+ "@nestjs/core": {
33
+ "optional": false
34
+ },
35
+ "@dismissible/nestjs-dismissible": {
36
+ "optional": false
37
+ },
38
+ "@dismissible/nestjs-logger": {
39
+ "optional": false
40
+ }
41
+ },
42
+ "keywords": [
43
+ "nestjs",
44
+ "jwt",
45
+ "authentication",
46
+ "jwks",
47
+ "oidc",
48
+ "dismissible",
49
+ "hook"
50
+ ],
51
+ "author": "",
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/DismissibleIo/dismissible-api"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }
package/project.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "jwt-auth-hook",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/jwt-auth-hook/src",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "targets": {
8
+ "build": {
9
+ "executor": "@nx/js:tsc",
10
+ "outputs": ["{options.outputPath}"],
11
+ "options": {
12
+ "outputPath": "dist/libs/jwt-auth-hook",
13
+ "main": "libs/jwt-auth-hook/src/index.ts",
14
+ "tsConfig": "libs/jwt-auth-hook/tsconfig.lib.json",
15
+ "assets": ["libs/jwt-auth-hook/package.json", "libs/jwt-auth-hook/README.md"],
16
+ "generatePackageJson": true
17
+ }
18
+ },
19
+ "lint": {
20
+ "executor": "@nx/eslint:lint",
21
+ "outputs": ["{options.outputFile}"],
22
+ "options": {
23
+ "lintFilePatterns": ["libs/jwt-auth-hook/**/*.ts"]
24
+ }
25
+ },
26
+ "test": {
27
+ "executor": "@nx/jest:jest",
28
+ "outputs": ["{workspaceRoot}/coverage/libs/jwt-auth-hook"],
29
+ "options": {
30
+ "jestConfig": "libs/jwt-auth-hook/jest.config.ts",
31
+ "passWithNoTests": true
32
+ }
33
+ },
34
+ "npm-publish": {
35
+ "executor": "nx:run-commands",
36
+ "options": {
37
+ "command": "npm publish --access public",
38
+ "cwd": "dist/libs/jwt-auth-hook"
39
+ }
40
+ }
41
+ }
42
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './jwt-auth-hook.config';
2
+ export * from './jwt-auth-hook.module';
3
+ export * from './jwt-auth.hook';
4
+ export * from './jwt-auth.service';
@@ -0,0 +1,158 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { JwtAuthHookConfig } from './jwt-auth-hook.config';
5
+
6
+ describe('JwtAuthHookConfig', () => {
7
+ describe('enabled property', () => {
8
+ it('should transform boolean true to true', async () => {
9
+ const config = plainToInstance(JwtAuthHookConfig, {
10
+ enabled: true,
11
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
12
+ });
13
+
14
+ expect(config.enabled).toBe(true);
15
+ });
16
+
17
+ it('should transform boolean false to false', async () => {
18
+ const config = plainToInstance(JwtAuthHookConfig, {
19
+ enabled: false,
20
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
21
+ });
22
+
23
+ expect(config.enabled).toBe(false);
24
+ });
25
+
26
+ it('should transform string "true" to boolean true', async () => {
27
+ const config = plainToInstance(JwtAuthHookConfig, {
28
+ enabled: 'true',
29
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
30
+ });
31
+
32
+ expect(config.enabled).toBe(true);
33
+ });
34
+
35
+ it('should transform string "false" to boolean false', async () => {
36
+ const config = plainToInstance(JwtAuthHookConfig, {
37
+ enabled: 'false',
38
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
39
+ });
40
+
41
+ expect(config.enabled).toBe(false);
42
+ });
43
+
44
+ it('should transform string "True" (case insensitive) to boolean true', async () => {
45
+ const config = plainToInstance(JwtAuthHookConfig, {
46
+ enabled: 'True',
47
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
48
+ });
49
+
50
+ expect(config.enabled).toBe(true);
51
+ });
52
+
53
+ it('should convert non-true string values to false', async () => {
54
+ const config = plainToInstance(JwtAuthHookConfig, {
55
+ enabled: 'other',
56
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
57
+ });
58
+
59
+ // The transform converts string values: 'true' -> true, anything else -> false
60
+ expect(config.enabled).toBe(false);
61
+ });
62
+ });
63
+
64
+ describe('wellKnownUrl validation', () => {
65
+ it('should require wellKnownUrl when enabled is true', async () => {
66
+ const config = plainToInstance(JwtAuthHookConfig, {
67
+ enabled: true,
68
+ });
69
+
70
+ const errors = await validate(config);
71
+ expect(errors.length).toBeGreaterThan(0);
72
+ expect(errors.some((e) => e.property === 'wellKnownUrl')).toBe(true);
73
+ });
74
+
75
+ it('should not require wellKnownUrl when enabled is false', async () => {
76
+ const config = plainToInstance(JwtAuthHookConfig, {
77
+ enabled: false,
78
+ });
79
+
80
+ const errors = await validate(config);
81
+ // wellKnownUrl should not be required when enabled is false
82
+ expect(errors.some((e) => e.property === 'wellKnownUrl')).toBe(false);
83
+ });
84
+
85
+ it('should validate wellKnownUrl is a valid URL', async () => {
86
+ const config = plainToInstance(JwtAuthHookConfig, {
87
+ enabled: true,
88
+ wellKnownUrl: 'not-a-valid-url',
89
+ });
90
+
91
+ const errors = await validate(config);
92
+ expect(errors.length).toBeGreaterThan(0);
93
+ expect(errors.some((e) => e.property === 'wellKnownUrl')).toBe(true);
94
+ });
95
+ });
96
+
97
+ describe('optional properties', () => {
98
+ it('should accept optional issuer', async () => {
99
+ const config = plainToInstance(JwtAuthHookConfig, {
100
+ enabled: true,
101
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
102
+ issuer: 'https://auth.example.com',
103
+ });
104
+
105
+ expect(config.issuer).toBe('https://auth.example.com');
106
+ });
107
+
108
+ it('should accept optional audience', async () => {
109
+ const config = plainToInstance(JwtAuthHookConfig, {
110
+ enabled: true,
111
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
112
+ audience: 'my-api',
113
+ });
114
+
115
+ expect(config.audience).toBe('my-api');
116
+ });
117
+
118
+ it('should accept optional algorithms array', async () => {
119
+ const config = plainToInstance(JwtAuthHookConfig, {
120
+ enabled: true,
121
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
122
+ algorithms: ['RS256', 'RS384'],
123
+ });
124
+
125
+ expect(config.algorithms).toEqual(['RS256', 'RS384']);
126
+ });
127
+
128
+ it('should transform jwksCacheDuration to number', async () => {
129
+ const config = plainToInstance(JwtAuthHookConfig, {
130
+ enabled: true,
131
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
132
+ jwksCacheDuration: '600000',
133
+ });
134
+
135
+ expect(config.jwksCacheDuration).toBe(600000);
136
+ });
137
+
138
+ it('should transform requestTimeout to number', async () => {
139
+ const config = plainToInstance(JwtAuthHookConfig, {
140
+ enabled: true,
141
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
142
+ requestTimeout: '30000',
143
+ });
144
+
145
+ expect(config.requestTimeout).toBe(30000);
146
+ });
147
+
148
+ it('should transform priority to number', async () => {
149
+ const config = plainToInstance(JwtAuthHookConfig, {
150
+ enabled: true,
151
+ wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
152
+ priority: '-100',
153
+ });
154
+
155
+ expect(config.priority).toBe(-100);
156
+ });
157
+ });
158
+ });
@@ -0,0 +1,94 @@
1
+ import {
2
+ IsString,
3
+ IsUrl,
4
+ IsOptional,
5
+ IsArray,
6
+ IsNumber,
7
+ IsBoolean,
8
+ ValidateIf,
9
+ } from 'class-validator';
10
+ import { Type } from 'class-transformer';
11
+ import { TransformBoolean } from '@dismissible/nestjs-validation';
12
+
13
+ /**
14
+ * Injection token for JWT auth hook configuration.
15
+ */
16
+ export const JWT_AUTH_HOOK_CONFIG = Symbol('JWT_AUTH_HOOK_CONFIG');
17
+
18
+ /**
19
+ * Configuration options for JWT authentication hook.
20
+ */
21
+ export class JwtAuthHookConfig {
22
+ @IsBoolean()
23
+ @TransformBoolean()
24
+ public readonly enabled!: boolean;
25
+
26
+ /**
27
+ * The OpenID Connect well-known URL (e.g., https://auth.example.com/.well-known/openid-configuration).
28
+ * The JWKS URI will be fetched from this endpoint.
29
+ */
30
+ @ValidateIf((o) => o.enabled === true)
31
+ @IsUrl()
32
+ public readonly wellKnownUrl!: string;
33
+
34
+ /**
35
+ * Optional: Expected issuer claim (iss) to validate.
36
+ * If not provided, issuer validation is skipped.
37
+ */
38
+ @IsOptional()
39
+ @IsString()
40
+ public readonly issuer?: string;
41
+
42
+ /**
43
+ * Optional: Expected audience claim (aud) to validate.
44
+ * If not provided, audience validation is skipped.
45
+ */
46
+ @IsOptional()
47
+ @IsString()
48
+ public readonly audience?: string;
49
+
50
+ /**
51
+ * Optional: Allowed algorithms for JWT verification.
52
+ * Defaults to ['RS256'].
53
+ */
54
+ @IsOptional()
55
+ @IsArray()
56
+ @IsString({ each: true })
57
+ public readonly algorithms?: string[];
58
+
59
+ /**
60
+ * Optional: Cache duration in milliseconds for JWKS.
61
+ * Defaults to 600000 (10 minutes).
62
+ */
63
+ @IsOptional()
64
+ @IsNumber()
65
+ @Type(() => Number)
66
+ public readonly jwksCacheDuration?: number;
67
+
68
+ /**
69
+ * Optional: Request timeout in milliseconds.
70
+ * Defaults to 30000 (30 seconds).
71
+ */
72
+ @IsOptional()
73
+ @IsNumber()
74
+ @Type(() => Number)
75
+ public readonly requestTimeout?: number;
76
+
77
+ /**
78
+ * Optional: Hook priority (lower numbers run first).
79
+ * Defaults to -100 (runs early for authentication).
80
+ */
81
+ @IsOptional()
82
+ @IsNumber()
83
+ @Type(() => Number)
84
+ public readonly priority?: number;
85
+
86
+ /**
87
+ * Optional: Verify that the userId parameter matches the JWT subject (sub) claim.
88
+ * Defaults to true for security. Set to false for service-to-service scenarios.
89
+ */
90
+ @IsOptional()
91
+ @IsBoolean()
92
+ @TransformBoolean(true) // Default to true if not provided
93
+ public readonly verifyUserIdMatch?: boolean;
94
+ }
@@ -0,0 +1,79 @@
1
+ import { Module, DynamicModule, InjectionToken } from '@nestjs/common';
2
+ import { HttpModule } from '@nestjs/axios';
3
+ import { JwtAuthHook } from './jwt-auth.hook';
4
+ import { JwtAuthService } from './jwt-auth.service';
5
+ import { JWT_AUTH_HOOK_CONFIG, JwtAuthHookConfig } from './jwt-auth-hook.config';
6
+
7
+ /**
8
+ * Async module options for JWT auth hook.
9
+ */
10
+ export interface IJwtAuthHookModuleAsyncOptions {
11
+ useFactory: (...args: unknown[]) => JwtAuthHookConfig | Promise<JwtAuthHookConfig>;
12
+ inject?: InjectionToken[];
13
+ }
14
+
15
+ /**
16
+ * Module that provides JWT authentication hook for Dismissible.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { DismissibleModule } from '@dismissible/nestjs-dismissible';
21
+ * import { JwtAuthHookModule, JwtAuthHook } from '@dismissible/nestjs-jwt-auth-hook';
22
+ *
23
+ * @Module({
24
+ * imports: [
25
+ * JwtAuthHookModule.forRoot({
26
+ * wellKnownUrl: 'https://auth.example.com/.well-known/openid-configuration',
27
+ * issuer: 'https://auth.example.com',
28
+ * audience: 'my-api',
29
+ * }),
30
+ * DismissibleModule.forRoot({
31
+ * hooks: [JwtAuthHook],
32
+ * // ... other options
33
+ * }),
34
+ * ],
35
+ * })
36
+ * export class AppModule {}
37
+ * ```
38
+ */
39
+ @Module({})
40
+ export class JwtAuthHookModule {
41
+ static forRoot(config: JwtAuthHookConfig): DynamicModule {
42
+ return {
43
+ module: JwtAuthHookModule,
44
+ imports: [HttpModule],
45
+ providers: [
46
+ {
47
+ provide: JWT_AUTH_HOOK_CONFIG,
48
+ useValue: config,
49
+ },
50
+ JwtAuthService,
51
+ JwtAuthHook,
52
+ ],
53
+ exports: [JwtAuthHook, JwtAuthService, JWT_AUTH_HOOK_CONFIG],
54
+ global: true,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Create module with async configuration.
60
+ * Useful when config values come from environment or other async sources.
61
+ */
62
+ static forRootAsync(options: IJwtAuthHookModuleAsyncOptions): DynamicModule {
63
+ return {
64
+ module: JwtAuthHookModule,
65
+ imports: [HttpModule],
66
+ providers: [
67
+ {
68
+ provide: JWT_AUTH_HOOK_CONFIG,
69
+ useFactory: options.useFactory,
70
+ inject: options.inject ?? [],
71
+ },
72
+ JwtAuthService,
73
+ JwtAuthHook,
74
+ ],
75
+ exports: [JwtAuthHook, JwtAuthService, JWT_AUTH_HOOK_CONFIG],
76
+ global: true,
77
+ };
78
+ }
79
+ }