Skip to content

Commit 7a17d11

Browse files
committed
fix(authentication-service): added cache layer to the jwks implementation
1 parent 20257a1 commit 7a17d11

File tree

44 files changed

+1098
-108
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1098
-108
lines changed

packages/core/src/components/bearer-verifier/component.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import {Binding, Component, inject, ProviderMap} from '@loopback/core';
66
import {Class, Model, Repository} from '@loopback/repository';
77
import {Strategies} from 'loopback4-authentication';
8-
import {JwtKeysRepository} from '../../repositories';
98
import {ILogger, LOGGER} from '../logger-extension';
109
import {
1110
BearerVerifierBindings,
@@ -17,7 +16,11 @@ import {FacadesBearerAsymmetricTokenVerifyProvider} from './providers/facades-be
1716
import {FacadesBearerTokenVerifyProvider} from './providers/facades-bearer-token-verify.provider';
1817
import {ServicesBearerAsymmetricTokenVerifyProvider} from './providers/services-bearer-asym-token-verifier';
1918
import {ServicesBearerTokenVerifyProvider} from './providers/services-bearer-token-verify.provider';
20-
import {RevokedTokenRepository} from './repositories';
19+
import {
20+
JwtKeysRepository,
21+
PublicKeysRepository,
22+
RevokedTokenRepository,
23+
} from './repositories';
2124

2225
export class BearerVerifierComponent implements Component {
2326
constructor(
@@ -27,7 +30,11 @@ export class BearerVerifierComponent implements Component {
2730
) {
2831
this.providers = {};
2932

30-
this.repositories = [RevokedTokenRepository, JwtKeysRepository];
33+
this.repositories = [
34+
RevokedTokenRepository,
35+
JwtKeysRepository,
36+
PublicKeysRepository,
37+
];
3138
this.models = [RevokedToken, JwtKeys];
3239

3340
if (this.config && this.config.type === BearerVerifierType.service) {

packages/core/src/components/bearer-verifier/providers/facades-bearer-asym-token-verify.provider.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,19 @@ import {
1414
} from 'loopback4-authentication';
1515
import moment from 'moment';
1616
import * as jose from 'node-jose';
17-
import {JwtKeysRepository} from '../../../repositories';
1817
import {ILogger, LOGGER} from '../../logger-extension';
1918
import {IAuthUserWithPermissions} from '../keys';
20-
import {RevokedTokenRepository} from '../repositories';
19+
import {PublicKeysRepository, RevokedTokenRepository} from '../repositories';
2120

2221
export class FacadesBearerAsymmetricTokenVerifyProvider
2322
implements Provider<VerifyFunction.BearerFn>
2423
{
2524
constructor(
2625
@repository(RevokedTokenRepository)
2726
public revokedTokenRepository: RevokedTokenRepository,
27+
@repository(PublicKeysRepository)
28+
public publicKeysRepository: PublicKeysRepository,
2829
@inject(LOGGER.LOGGER_INJECT) private readonly logger: ILogger,
29-
@repository(JwtKeysRepository)
30-
public jwtKeysRepo: JwtKeysRepository,
3130
@inject(AuthenticationBindings.USER_MODEL, {optional: true})
3231
public authUserModel?: Constructor<EntityWithIdentifier & IAuthUser>,
3332
) {}
@@ -53,14 +52,10 @@ export class FacadesBearerAsymmetricTokenVerifyProvider
5352
if (!decoded) {
5453
throw new Error('Token is not valid');
5554
}
56-
const kid = decoded?.header.kid;
55+
const kid = decoded?.header.kid ?? '';
5756

58-
// Load the JWKS
59-
const key = await this.jwtKeysRepo.findOne({
60-
where: {
61-
keyId: kid,
62-
},
63-
});
57+
// Get the public key from the cache
58+
const key = await this.publicKeysRepository.get(kid);
6459

6560
if (!key) {
6661
throw new Error('Key not found for verification');

packages/core/src/components/bearer-verifier/providers/services-bearer-asym-token-verifier.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import {
1414
} from 'loopback4-authentication';
1515
import moment from 'moment-timezone';
1616
import * as jose from 'node-jose';
17-
import {JwtKeysRepository} from '../../../repositories';
1817
import {ILogger, LOGGER} from '../../logger-extension';
1918
import {IAuthUserWithPermissions} from '../keys';
19+
import {JwtKeysRepository} from '../repositories';
2020

2121
export class ServicesBearerAsymmetricTokenVerifyProvider
2222
implements Provider<VerifyFunction.BearerFn>

packages/core/src/components/bearer-verifier/repositories/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
//
33
// This software is released under the MIT License.
44
// https://opensource.org/licenses/MIT
5+
export * from './jwt-keys.repository';
6+
export * from './public-keys.repository';
57
export * from './revoked-token.repository';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) 2023 Sourcefuse Technologies
2+
//
3+
// This software is released under the MIT License.
4+
// https://opensource.org/licenses/MIT
5+
import {inject} from '@loopback/core';
6+
import {DefaultKeyValueRepository, juggler} from '@loopback/repository';
7+
import {AuthCacheSourceName} from '../../../types';
8+
import {JwtKeys} from '../models';
9+
10+
export class PublicKeysRepository extends DefaultKeyValueRepository<JwtKeys> {
11+
constructor(
12+
@inject(`datasources.${AuthCacheSourceName}`)
13+
dataSource: juggler.DataSource,
14+
) {
15+
super(JwtKeys, dataSource);
16+
}
17+
}

packages/core/src/repositories/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@
55
export * from './default-soft-crud.repository.base';
66
export * from './default-transactional-user-modify-repository.base';
77
export * from './default-user-modify-crud.repository.base';
8-
export * from './jwt-keys.repository';

packages/core/src/repositories/jwt-keys.repository.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

services/audit-service/README.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ export class MyApplication extends BootMixin(
6363

6464
### Asymmetric Token Signing and Verification
6565

66-
If you are using asymmetric token signing and verification, you need to create a datasource for auth database. Example datasource file for auth:-
66+
If you are using asymmetric token signing and verification, you should have auth datasource present in the service and auth redis datasource on the facade level. Example datasource file for auth database is:-
67+
68+
Auth DB datasource :-
6769

6870
```ts
6971
import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
@@ -121,6 +123,71 @@ export class AuthDataSource
121123
}
122124
```
123125

126+
Auth Cache Redis Datasource:-
127+
128+
```ts
129+
import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
130+
import {AnyObject, juggler} from '@loopback/repository';
131+
import {readFileSync} from 'fs';
132+
import {AuthCacheSourceName} from '@sourceloop/core';
133+
134+
const config = {
135+
name: process.env.REDIS_NAME,
136+
connector: 'kv-redis',
137+
host: process.env.REDIS_HOST,
138+
port: process.env.REDIS_PORT,
139+
password: process.env.REDIS_PASSWORD,
140+
db: process.env.REDIS_DATABASE,
141+
url: process.env.REDIS_URL,
142+
tls:
143+
+process.env.REDIS_TLS_ENABLED! && process.env.REDIS_TLS_CERT
144+
? {
145+
ca: readFileSync(process.env.REDIS_TLS_CERT),
146+
}
147+
: undefined,
148+
sentinels:
149+
+process.env.REDIS_HAS_SENTINELS! && process.env.REDIS_SENTINELS
150+
? JSON.parse(process.env.REDIS_SENTINELS)
151+
: undefined,
152+
sentinelPassword:
153+
+process.env.REDIS_HAS_SENTINELS! && process.env.REDIS_SENTINEL_PASSWORD
154+
? process.env.REDIS_SENTINEL_PASSWORD
155+
: undefined,
156+
role:
157+
+process.env.REDIS_HAS_SENTINELS! && process.env.REDIS_SENTINEL_ROLE
158+
? process.env.REDIS_SENTINEL_ROLE
159+
: undefined,
160+
};
161+
162+
@lifeCycleObserver('datasource')
163+
export class RedisDataSource
164+
extends juggler.DataSource
165+
implements LifeCycleObserver
166+
{
167+
static readonly dataSourceName = AuthCacheSourceName;
168+
static readonly defaultConfig = config;
169+
170+
constructor(
171+
@inject(`datasources.config.${process.env.REDIS_NAME}`, {optional: true})
172+
dsConfig: AnyObject = config,
173+
) {
174+
if (
175+
+process.env.REDIS_HAS_SENTINELS! &&
176+
!!process.env.REDIS_SENTINEL_HOST &&
177+
!!process.env.REDIS_SENTINEL_PORT
178+
) {
179+
dsConfig.sentinels = [
180+
{
181+
host: process.env.REDIS_SENTINEL_HOST,
182+
port: +process.env.REDIS_SENTINEL_PORT,
183+
},
184+
];
185+
}
186+
super(dsConfig);
187+
}
188+
}
189+
```
190+
124191
### Set the [environment variables](#environment-variables)
125192

126193
The examples below show a common configuration for a PostgreSQL Database running locally.

services/authentication-service/src/__tests__/acceptance/login-activity.controller.acceptance.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Client, expect} from '@loopback/testlab';
2+
import {JwtKeysRepository} from '@sourceloop/core';
23
import {LoginType} from '../../enums';
3-
import {JwtKeysRepository, LoginActivityRepository} from '../../repositories';
4+
import {LoginActivityRepository} from '../../repositories';
45
import {TestingApplication} from '../fixtures/application';
56
import {JwtToken} from '../fixtures/datasources/userCredsAndPermission';
67
import {setupApplication} from './test-helper';

services/authentication-service/src/__tests__/fixtures/application.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
RestExplorerComponent,
1212
} from '@loopback/rest-explorer';
1313
import {ServiceMixin} from '@loopback/service-proxy';
14+
import {JwtKeysRepository, PublicKeysRepository} from '@sourceloop/core';
1415
import {Strategies} from 'loopback4-authentication';
1516
import {LocalPasswordStrategyFactoryProvider} from 'loopback4-authentication/passport-local';
1617
import {PassportOtpStrategyFactoryProvider} from 'loopback4-authentication/passport-otp';
@@ -61,6 +62,12 @@ export class TestingApplication extends BootMixin(
6162
);
6263
this.service(TestHelperService, {defaultScope: BindingScope.SINGLETON});
6364

65+
// add multiple repositories
66+
this.bind('repositories.JwtKeysRepository').toClass(JwtKeysRepository);
67+
this.bind('repositories.PublicKeysRepository').toClass(
68+
PublicKeysRepository,
69+
);
70+
6471
this.projectRoot = __dirname;
6572
// Customize @loopback/boot Booter Conventions here
6673
this.bootOptions = {

0 commit comments

Comments
 (0)