@quanticjs/auth-web-bff 5.1.0 → 5.3.0

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.
@@ -46,6 +46,7 @@ let BffController = class BffController {
46
46
  try {
47
47
  const { sessionId, returnTo } = await this.bffService.handleCallback(code, state, codeVerifier);
48
48
  res.cookie(this.bffService.getCookieName(), sessionId, this.bffService.getCookieOptions());
49
+ res.cookie(this.bffService.getCsrfCookieName(), this.bffService.generateCsrfToken(), this.bffService.getCsrfCookieOptions());
49
50
  res.redirect(returnTo);
50
51
  }
51
52
  catch {
@@ -53,6 +54,12 @@ let BffController = class BffController {
53
54
  }
54
55
  }
55
56
  async refresh(req, res) {
57
+ const csrfHeader = req.headers['x-csrf-token'];
58
+ const csrfCookie = req.cookies?.[this.bffService.getCsrfCookieName()];
59
+ if (!this.bffService.validateCsrf(csrfHeader, csrfCookie)) {
60
+ res.status(403).json({ message: 'Invalid CSRF token' });
61
+ return;
62
+ }
56
63
  const sessionId = req.cookies?.[this.bffService.getCookieName()];
57
64
  if (!sessionId) {
58
65
  res.status(401).json({ message: 'No session' });
@@ -66,12 +73,21 @@ let BffController = class BffController {
66
73
  res.json({ success: true });
67
74
  }
68
75
  async logout(req, res) {
76
+ const csrfHeader = req.headers['x-csrf-token'];
77
+ const csrfCookie = req.cookies?.[this.bffService.getCsrfCookieName()];
78
+ if (!this.bffService.validateCsrf(csrfHeader, csrfCookie)) {
79
+ res.status(403).json({ message: 'Invalid CSRF token' });
80
+ return;
81
+ }
69
82
  const cookieName = this.bffService.getCookieName();
70
83
  const sessionId = req.cookies?.[cookieName];
71
84
  if (sessionId) {
72
85
  await this.bffService.destroySession(sessionId);
73
86
  }
74
- res.clearCookie(cookieName, this.bffService.getCookieOptions()).json({ success: true });
87
+ res
88
+ .clearCookie(cookieName, this.bffService.getCookieOptions())
89
+ .clearCookie(this.bffService.getCsrfCookieName(), this.bffService.getCsrfCookieOptions())
90
+ .json({ success: true });
75
91
  }
76
92
  async me(req, res) {
77
93
  const cookieName = this.bffService.getCookieName();
@@ -10,6 +10,7 @@ export declare class BffService implements OnModuleInit {
10
10
  private readonly sessionTtl;
11
11
  private readonly sessionPrefix;
12
12
  private readonly cookieName;
13
+ private readonly csrfCookieName;
13
14
  constructor(options: BffModuleOptions, redis: Redis | undefined);
14
15
  onModuleInit(): Promise<void>;
15
16
  getAuthorizationUrl(returnTo?: string): {
@@ -27,6 +28,10 @@ export declare class BffService implements OnModuleInit {
27
28
  getUserInfo(sessionId: string): Promise<Record<string, unknown> | null>;
28
29
  getCookieOptions(): Record<string, unknown>;
29
30
  getCookieName(): string;
31
+ getCsrfCookieName(): string;
32
+ getCsrfCookieOptions(): Record<string, unknown>;
33
+ generateCsrfToken(): string;
34
+ validateCsrf(headerValue: string | undefined, cookieValue: string | undefined): boolean;
30
35
  private saveSession;
31
36
  private toPublicKeycloakUrl;
32
37
  private getCallbackUrl;
@@ -16,6 +16,7 @@ exports.BffService = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const openid_client_1 = require("openid-client");
18
18
  const uuid_1 = require("uuid");
19
+ const crypto_1 = require("crypto");
19
20
  const core_1 = require("@quanticjs/core");
20
21
  const interfaces_1 = require("./interfaces");
21
22
  function extractRealmRoles(accessToken) {
@@ -38,12 +39,14 @@ let BffService = class BffService {
38
39
  sessionTtl;
39
40
  sessionPrefix;
40
41
  cookieName;
42
+ csrfCookieName;
41
43
  constructor(options, redis) {
42
44
  this.options = options;
43
45
  this.redis = redis;
44
46
  this.sessionTtl = options.session?.ttlSeconds ?? 7 * 24 * 3600;
45
47
  this.sessionPrefix = options.session?.prefix ?? 'session:';
46
- this.cookieName = options.session?.cookieName ?? 'sid';
48
+ this.cookieName = options.session?.cookieName ?? '__Host-sid';
49
+ this.csrfCookieName = options.csrf?.cookieName ?? '__Host-csrf';
47
50
  }
48
51
  async onModuleInit() {
49
52
  const { keycloak } = this.options;
@@ -166,7 +169,7 @@ let BffService = class BffService {
166
169
  return {
167
170
  httpOnly: true,
168
171
  secure: process.env.NODE_ENV === 'production',
169
- sameSite: 'lax',
172
+ sameSite: 'strict',
170
173
  path: '/',
171
174
  maxAge: this.sessionTtl * 1000,
172
175
  };
@@ -174,6 +177,24 @@ let BffService = class BffService {
174
177
  getCookieName() {
175
178
  return this.cookieName;
176
179
  }
180
+ getCsrfCookieName() {
181
+ return this.csrfCookieName;
182
+ }
183
+ getCsrfCookieOptions() {
184
+ return {
185
+ httpOnly: false,
186
+ secure: process.env.NODE_ENV === 'production',
187
+ sameSite: 'strict',
188
+ path: '/',
189
+ maxAge: this.sessionTtl * 1000,
190
+ };
191
+ }
192
+ generateCsrfToken() {
193
+ return (0, crypto_1.randomBytes)(32).toString('base64url');
194
+ }
195
+ validateCsrf(headerValue, cookieValue) {
196
+ return !!headerValue && !!cookieValue && headerValue === cookieValue;
197
+ }
177
198
  async saveSession(sessionId, data) {
178
199
  if (!this.redis)
179
200
  throw new Error('Redis not available');
@@ -11,6 +11,9 @@ export interface BffModuleOptions {
11
11
  cookieName?: string;
12
12
  prefix?: string;
13
13
  };
14
+ csrf?: {
15
+ cookieName?: string;
16
+ };
14
17
  publicUrl: string;
15
18
  callbackPath?: string;
16
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quanticjs/auth-web-bff",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "description": "BFF authentication module — Keycloak OIDC, Redis sessions, httpOnly cookies",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  "clean": "rm -rf dist"
10
10
  },
11
11
  "dependencies": {
12
- "@quanticjs/core": "^5.1.0"
12
+ "@quanticjs/core": "^5.3.0"
13
13
  },
14
14
  "peerDependencies": {
15
15
  "@nestjs/common": "^10.0.0 || ^11.0.0",