@lwrjs/auth-middleware 0.9.0-alpha.7 → 0.9.0-alpha.9

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 CHANGED
@@ -66,16 +66,6 @@ Before using the LWR authentication middleware, the app developer must set up a
66
66
  - Make selections for a "Web Server Flow" (not Device or JWT flows).
67
67
  - Set the "Callback URL" to the [origin](https://developer.mozilla.org/en-US/docs/Web/API/URL/origin) of the LWR app with an `/oauth` path appended (e.g. `https://website.com/oauth`).
68
68
  2. Make a note of the "Consumer Key" (i.e. Client ID) and "Consumer Secret" (i.e. Client Secret) (or find them later in the App Manager).
69
- 3. Let users self-authorize:
70
- - In Setup, go to "Manage Connected Apps"
71
- - Click "Edit" next to the new Connected App name
72
- - Set "Permitted Users" to "All users may self-authorize"
73
- - Click "Save"
74
- 4. Allow Profiles access to the Connected App:
75
- - In Setup, go to "Profiles"
76
- - Click "Edit" next to the desired profile
77
- - Select the Connected App under "Enable Connected Apps"
78
- - Click "Save"
79
69
 
80
70
  ## Details
81
71
 
@@ -128,7 +118,7 @@ The LWR server takes the following steps in order to authenticate and make autho
128
118
  4. The user sees a login page in their browser and enters their username and password.
129
119
  5. The Connected app makes a request to the middleware's redirect URI (i.e. the `/oauth` endpoint) which includes the authorization code.
130
120
  6. The middleware POSTs to the org's `/services/oauth2/token` endpoint to request an OAuth token.
131
- 7. Upon receiving the token, the middleware adds it to a [`httpOnly` cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) and redirects to the LWR app (i.e. `app_path`).
121
+ 7. Upon receiving the token, the middleware adds it to a secure [`httpOnly` cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) and redirects to the LWR app (i.e. `app_path`).
132
122
  8. When the middleware receives a request to the proxy endpoint, it forwards the request to the Salesforce org authorized with the OAuth token:
133
123
 
134
124
  ```js
@@ -10,14 +10,17 @@ __markAsModule(exports);
10
10
  __export(exports, {
11
11
  COOKIE_NAME: () => COOKIE_NAME,
12
12
  LOGIN_ENDPOINT: () => LOGIN_ENDPOINT,
13
+ MESSAGE_PREFIX: () => MESSAGE_PREFIX,
13
14
  PROXY_ENDPOINT: () => PROXY_ENDPOINT,
14
15
  REDIRECT_ENDPOINT: () => REDIRECT_ENDPOINT,
15
16
  buildProxyRequest: () => buildProxyRequest,
16
17
  buildRedirectUrl: () => buildRedirectUrl,
17
- getAppPathAsState: () => getAppPathAsState,
18
+ generateStateString: () => generateStateString,
19
+ getAppPath: () => getAppPath,
18
20
  getOauthInfoFromCookie: () => getOauthInfoFromCookie,
19
21
  getPlatformWebServerEnvVars: () => getPlatformWebServerEnvVars
20
22
  });
23
+ var MESSAGE_PREFIX = "Web Server OAuth Middleware - ";
21
24
  var LOGIN_ENDPOINT = "/login";
22
25
  var REDIRECT_ENDPOINT = "/oauth";
23
26
  var PROXY_ENDPOINT = "/services/data";
@@ -43,13 +46,16 @@ function getPlatformWebServerEnvVars() {
43
46
  }
44
47
  return {myDomain, clientKey, clientSecret};
45
48
  }
46
- function getAppPathAsState(req) {
49
+ function generateStateString() {
50
+ return `s${Math.floor(Math.random() * 65536).toString(16)}`;
51
+ }
52
+ function getAppPath(req) {
47
53
  let appPath = req.query[REDIRECT_QUERY];
48
54
  if (appPath && !decodeURIComponent(appPath).startsWith("/")) {
49
55
  console.warn('The "app_path" parameter must be a relative path staring with "/". Defaulting to "app_path" = "/".');
50
56
  appPath = void 0;
51
57
  }
52
- return appPath ? `&state=${appPath}` : "";
58
+ return appPath ? decodeURIComponent(appPath) : "/";
53
59
  }
54
60
  function getOauthInfoFromCookie(req) {
55
61
  const oauthInfoStr = req.cookie(COOKIE_NAME);
@@ -24,24 +24,34 @@ var __toModule = (module2) => {
24
24
  // packages/@lwrjs/auth-middleware/src/web-server.ts
25
25
  __markAsModule(exports);
26
26
  __export(exports, {
27
- platformWebServerAuthMiddleware: () => platformWebServerAuthMiddleware
27
+ platformWebServerAuthMiddleware: () => platformWebServerAuthMiddleware,
28
+ stateMap: () => stateMap
28
29
  });
29
30
  var import_node_fetch = __toModule(require("node-fetch"));
30
31
  var import_utils = __toModule(require("./utils.cjs"));
31
- var MESSAGE_PREFIX = "Web Server OAuth Middleware - ";
32
+ var stateMap = {};
32
33
  function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.PROXY_ENDPOINT} = {}) {
33
34
  let redirectUri;
34
35
  const {myDomain, clientKey, clientSecret} = (0, import_utils.getPlatformWebServerEnvVars)();
35
36
  server.get(import_utils.LOGIN_ENDPOINT, (req, res) => {
36
37
  redirectUri = redirectUri || (0, import_utils.buildRedirectUrl)(req);
37
- const state = (0, import_utils.getAppPathAsState)(req);
38
+ const stateStr = (0, import_utils.generateStateString)();
39
+ const appPath = (0, import_utils.getAppPath)(req);
40
+ stateMap[stateStr] = appPath;
38
41
  const loginUri = `${myDomain}/services/oauth2/authorize?response_type=code`;
39
- res.set({Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}${state}`});
42
+ res.set({
43
+ Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}&state=${stateStr}`
44
+ });
40
45
  res.sendStatus(302);
41
46
  });
42
47
  server.get(import_utils.REDIRECT_ENDPOINT, async (req, res) => {
43
48
  const {code, state} = req.query;
44
- const appPath = state ? decodeURIComponent(state) : "/";
49
+ const appPath = stateMap[state];
50
+ if (!appPath) {
51
+ res.status(400).send(`${import_utils.MESSAGE_PREFIX}invalid login attempt.`);
52
+ return;
53
+ }
54
+ delete stateMap[state];
45
55
  const response = await (0, import_node_fetch.default)(`${myDomain}/services/oauth2/token`, {
46
56
  method: "POST",
47
57
  headers: {"Content-Type": "application/x-www-form-urlencoded", Accept: "application/json"},
@@ -51,11 +61,14 @@ function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.P
51
61
  const data = await (contentType.includes("application/json") ? response.json() : response.text());
52
62
  if (response.ok) {
53
63
  const {access_token, instance_url} = data;
54
- res.cookie(import_utils.COOKIE_NAME, JSON.stringify({access_token, instance_url}), {httpOnly: true});
64
+ res.cookie(import_utils.COOKIE_NAME, JSON.stringify({access_token, instance_url}), {
65
+ httpOnly: true,
66
+ secure: true
67
+ });
55
68
  res.set({Location: appPath});
56
69
  res.sendStatus(302);
57
70
  } else {
58
- const errorMsg = `${MESSAGE_PREFIX}could not authenticate.`;
71
+ const errorMsg = `${import_utils.MESSAGE_PREFIX}could not authenticate.`;
59
72
  console.error(errorMsg, data);
60
73
  res.status(response.status).send(errorMsg);
61
74
  }
@@ -69,7 +82,7 @@ function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.P
69
82
  const data = await (contentType.includes("application/json") ? proxyRes.json() : proxyRes.text());
70
83
  res.status(proxyRes.status).send(data);
71
84
  } else {
72
- res.status(401).send(`${MESSAGE_PREFIX}user is not authenticated.`);
85
+ res.status(401).send(`${import_utils.MESSAGE_PREFIX}user is not authenticated.`);
73
86
  }
74
87
  });
75
88
  }
@@ -1,12 +1,14 @@
1
1
  import type { MiddlewareRequest, Headers } from '@lwrjs/types';
2
2
  import type { OAuthInfo, RequestOptions, PlatformWebServerInput } from './types.js';
3
+ export declare const MESSAGE_PREFIX = "Web Server OAuth Middleware - ";
3
4
  export declare const LOGIN_ENDPOINT = "/login";
4
5
  export declare const REDIRECT_ENDPOINT = "/oauth";
5
6
  export declare const PROXY_ENDPOINT = "/services/data";
6
7
  export declare const COOKIE_NAME = "lwr_web_server_oauth_info";
7
8
  export declare function buildRedirectUrl(req: MiddlewareRequest): string;
8
9
  export declare function getPlatformWebServerEnvVars(): PlatformWebServerInput;
9
- export declare function getAppPathAsState(req: MiddlewareRequest): string;
10
+ export declare function generateStateString(): string;
11
+ export declare function getAppPath(req: MiddlewareRequest): string;
10
12
  export declare function getOauthInfoFromCookie(req: MiddlewareRequest): OAuthInfo | undefined;
11
13
  export declare function buildProxyRequest(req: MiddlewareRequest, { access_token, instance_url }: OAuthInfo, existingHeader: Headers): RequestOptions;
12
14
  //# sourceMappingURL=utils.d.ts.map
package/build/es/utils.js CHANGED
@@ -1,3 +1,4 @@
1
+ export const MESSAGE_PREFIX = 'Web Server OAuth Middleware - ';
1
2
  export const LOGIN_ENDPOINT = '/login';
2
3
  export const REDIRECT_ENDPOINT = '/oauth';
3
4
  export const PROXY_ENDPOINT = '/services/data';
@@ -27,14 +28,20 @@ export function getPlatformWebServerEnvVars() {
27
28
  }
28
29
  return { myDomain, clientKey, clientSecret };
29
30
  }
30
- // Parse the app_path query param, validate it, and return it as a state query param
31
- export function getAppPathAsState(req) {
31
+ // Create a random string to use as the OAuth state value
32
+ // This value is saved and used to verify the request in the REDIRECT_ENDPOINT
33
+ // See: https://auth0.com/docs/secure/attack-protection/state-parameters
34
+ export function generateStateString() {
35
+ return `s${Math.floor(Math.random() * 0x10000).toString(16)}`;
36
+ }
37
+ // Parse the app_path query param, validate it, and return it decoded. Default = "/"
38
+ export function getAppPath(req) {
32
39
  let appPath = req.query[REDIRECT_QUERY];
33
40
  if (appPath && !decodeURIComponent(appPath).startsWith('/')) {
34
41
  console.warn('The "app_path" parameter must be a relative path staring with "/". Defaulting to "app_path" = "/".');
35
42
  appPath = undefined;
36
43
  }
37
- return appPath ? `&state=${appPath}` : '';
44
+ return appPath ? decodeURIComponent(appPath) : '/';
38
45
  }
39
46
  // Get the token and url from the cookie
40
47
  export function getOauthInfoFromCookie(req) {
@@ -1,5 +1,8 @@
1
1
  import type { PublicAppServer, ServerTypes } from '@lwrjs/types';
2
2
  import type { PlatformWebServerOptions } from './types.js';
3
+ export declare const stateMap: {
4
+ [key: string]: string;
5
+ };
3
6
  /**
4
7
  * Web Server OAuth middleware: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5
5
8
  * Accepts the following environment variables:
@@ -1,6 +1,6 @@
1
1
  import fetch from 'node-fetch';
2
- import { COOKIE_NAME, LOGIN_ENDPOINT, PROXY_ENDPOINT, REDIRECT_ENDPOINT, buildProxyRequest, buildRedirectUrl, getAppPathAsState, getOauthInfoFromCookie, getPlatformWebServerEnvVars, } from './utils.js';
3
- const MESSAGE_PREFIX = 'Web Server OAuth Middleware - ';
2
+ import { COOKIE_NAME, LOGIN_ENDPOINT, MESSAGE_PREFIX, PROXY_ENDPOINT, REDIRECT_ENDPOINT, buildProxyRequest, buildRedirectUrl, generateStateString, getAppPath, getOauthInfoFromCookie, getPlatformWebServerEnvVars, } from './utils.js';
3
+ export const stateMap = {};
4
4
  /**
5
5
  * Web Server OAuth middleware: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5
6
6
  * Accepts the following environment variables:
@@ -16,20 +16,30 @@ export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_
16
16
  // First, request an authorization code by redirecting to Salesforce login
17
17
  // The LWR app should call this endpoint when authentication is needed
18
18
  server.get(LOGIN_ENDPOINT, (req, res) => {
19
- // Build the redirect URI and save the app path in the OAuth state, if included
19
+ // Build the redirect URI and save the app path in the state map
20
20
  redirectUri = redirectUri || buildRedirectUrl(req);
21
- const state = getAppPathAsState(req);
21
+ const stateStr = generateStateString();
22
+ const appPath = getAppPath(req);
23
+ stateMap[stateStr] = appPath; // Save the state string for verification in step 2
22
24
  // Redirect to the Salesforce login page
23
25
  const loginUri = `${myDomain}/services/oauth2/authorize?response_type=code`;
24
- res.set({ Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}${state}` });
26
+ res.set({
27
+ Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}&state=${stateStr}`,
28
+ });
25
29
  res.sendStatus(302);
26
30
  });
27
31
  // Second, use the authorization code from the redirect URI to retrieve the token
28
32
  // This route MUST BE the "Callback URL" in the Connected App OAuth settings
29
33
  server.get(REDIRECT_ENDPOINT, async (req, res) => {
30
- // Parse the code and state (i.e. app_path) query params
34
+ // Parse the code and state string from the query params
31
35
  const { code, state } = req.query;
32
- const appPath = state ? decodeURIComponent(state) : '/';
36
+ const appPath = stateMap[state];
37
+ // If the state string is not found in the map, the request is invalid
38
+ if (!appPath) {
39
+ res.status(400).send(`${MESSAGE_PREFIX}invalid login attempt.`);
40
+ return;
41
+ }
42
+ delete stateMap[state];
33
43
  // Fetch the OAuth token
34
44
  const response = await fetch(`${myDomain}/services/oauth2/token`, {
35
45
  method: 'POST',
@@ -44,7 +54,10 @@ export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_
44
54
  if (response.ok) {
45
55
  // Store the OAuth info in a cookie and redirect to the app
46
56
  const { access_token, instance_url } = data;
47
- res.cookie(COOKIE_NAME, JSON.stringify({ access_token, instance_url }), { httpOnly: true });
57
+ res.cookie(COOKIE_NAME, JSON.stringify({ access_token, instance_url }), {
58
+ httpOnly: true,
59
+ secure: true,
60
+ });
48
61
  res.set({ Location: appPath });
49
62
  res.sendStatus(302);
50
63
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.9.0-alpha.7",
7
+ "version": "0.9.0-alpha.9",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -33,12 +33,12 @@
33
33
  "node-fetch": "^2.6.1"
34
34
  },
35
35
  "devDependencies": {
36
- "@lwrjs/server": "0.9.0-alpha.7",
37
- "@lwrjs/types": "0.9.0-alpha.7",
36
+ "@lwrjs/server": "0.9.0-alpha.9",
37
+ "@lwrjs/types": "0.9.0-alpha.9",
38
38
  "@types/node-fetch": "^2.5.12"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=14.15.4 <19"
42
42
  },
43
- "gitHead": "b442dafaf3416c8c86f0e05c1644b805aac3decd"
43
+ "gitHead": "93522592d25375bb20da156f864d153ffcd3ec88"
44
44
  }