forked from buchanan-edwards/azure-easy-auth-local
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathazure-easy-auth-local.js
152 lines (145 loc) · 5.61 KB
/
azure-easy-auth-local.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* @module azure-easy-auth-local.js
*
* This auth middleware is only used to support development on the localhost.
* When deployed, the /.auth endpoints, such as /.auth/me and /.auth/refresh,
* are handled by Azure App Service Authorization and Authentication and this
* module is never used. However, during development, your web app is running
* locally from your Express API but still needs to access these endpoints to
* get the access token for API authenticatio. But it cannot because they are
* blocked by CORS policies built into the browser.
*
* Instead of running the browser with security disabled (easy in Chrome but
* not so easy with Edge), we create a "fake" /.auth/* endpoint. The web app
* can access these and thinks that it is accessing the real thing because
* they are host-relative URLs. This middleware intercepts those requests and
* redirects them to this same app running in Azure but to the non-dotted
* versions of those same endpoints.
*
* Since the App Service framework has set the cookies, they will be sent to
* this middleware function, which then proxies them to the real endpoints.
* The data is then returned to the web app which thinks it succeeded and
* doesn't know it is running on the localhost.
*
* A valid question at this point is, why not just configure the allowed CORS
* origins using the Azure portal? The answer is that this feature only sets
* the Access-Control-Allow-Origin header and does not set the other required
* header, Access-Control-Allow-Credentials. This header must be set to true
* for the browser to send the AppServiceAuthSession cookie, authenticating
* the web app. The only work-around, until Azure allows this to be set, is
* to send these headers ourselves on endpoints we control, namely the ones
* provided by this middleware: /auth/me and /auth/refresh (without the dot).
*
* The flow, during development, is this:
* 1. The web app, running on the localhost, sends a GET /.auth/me request.
* 2. The server, also, running on the localhost, responds to the request
* and redirects the browser to the https://<azure-host>/auth/me endpoint.
* 3. This endpoint uses the cookie and makes the same request to the real
* https://<azure-host>/.auth/me endpoint. The data, along with the
* required CORS headers, is returned to the application.
*
* For this to work as designed, the app, with this middleware in place, must
* already have been deployed to Azure. Note that this works the same for the
* /.auth/refresh endpoint.
*
* Now the redirect from the fake endpoints to the stub endpoints, which
* proxies them to the real endpoints, will work. Again, this is only to
* support localhost development, but that is critical versus redeploying
* to Azure for every source code change that could (and should) be tested
* locally before committing to the source code repository for deployment.
*
* @author Frank Hellwig <[email protected]
* @license MIT
* @copyright 2018 Buchanan & Edwards, Inc. All rights reserved.
*/
'use strict';
const axios = require('axios');
const cookie = require('cookie');
const APPLICATION_JSON = 'application/json';
const APP_SERVICE_AUTH_SESSION = 'AppServiceAuthSession';
/**
* Proxies the request to the host with the auth session cookie.
* For example, GET /auth/me (without the initial .) is proxied
* to GET http://{azureHost}/.auth/me (with the initial .), the real
* Azure endpoint that can handle the request.
*
* @param req request object
* @param azureHost host name of azure app service
*
*/
function proxyRequest(req, azureHost) {
const cookies = cookie.parse(req.headers.cookie || '');
const session = cookies[APP_SERVICE_AUTH_SESSION];
if (!session) {
const err = new Error(`No ${APP_SERVICE_AUTH_SESSION} cookie in request.`);
err.code = 400;
return Promise.reject(err);
}
const url = `https://${azureHost}/.${req.url.substr(1)}`;
return axios({
method: 'GET',
url: url,
headers: {
Accept: APPLICATION_JSON,
Cookie: `${APP_SERVICE_AUTH_SESSION}=${session}`
}
})
.then(response => {
return response.data;
})
.catch(err => {
const code = err.response ? err.response.status : 500;
err = new Error(err.message);
err.code = code;
return Promise.reject(err);
});
}
/**
* Adds the CORS headers that allow access by the local host.
*
* @param res response object
* @param localOrigin local origin for CORS headers
*
*/
function corsHeaders(res, localOrigin) {
res.set('Access-Control-Allow-Credentials', 'true');
res.set('Access-Control-Allow-Origin', localOrigin);
res.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
}
/**
* Returns a middleware function that will proxy requests to the
* specified host.
*
* @param azureHost host name of the app service
* @param localOrigin origin for CORS headers
*
*/
function middleware(azureHost, localOrigin) {
return function(req, res, next) {
const { method, url } = req;
if (method === 'GET') {
if (url.startsWith('/.auth/')) {
res.redirect(302, `https://${azureHost}/${url.substr(2)}`);
} else if (url.startsWith('/auth/')) {
proxyRequest(req, azureHost)
.then(data => {
corsHeaders(res, localOrigin);
res.json(data);
})
.catch(err => {
next(err);
});
} else {
next();
}
} else if (method === 'OPTIONS' && url.startsWith('/auth/')) {
corsHeaders(res, localOrigin);
res.status(204);
res.set('Content-Length', '0');
res.end();
} else {
next();
}
};
}
module.exports = middleware;