Skip to content

Commit 3c34b27

Browse files
committed
Merge pull request #4 from eschwim/master
A few usability changes and security fixes
2 parents b987532 + c4ef146 commit 3c34b27

File tree

2 files changed

+106
-24
lines changed

2 files changed

+106
-24
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ server {
5050
5151
set $ngo_client_id "abc-def.apps.googleusercontent.com";
5252
set $ngo_client_secret "abcdefg-123-xyz";
53+
set $ngo_token_secret "a very long randomish string";
5354
set $ngo_secure_cookies "true";
5455
access_by_lua_file "/etc/nginx/nginx-google-oauth/access.lua";
5556
}
@@ -61,6 +62,7 @@ variables are:
6162

6263
- **$ngo_client_id** This is the client id key
6364
- **$ngo_client_secret** This is the client secret
65+
- **$ngo_token_secret** The key used to encrypt the session token stored in the user cookie. Should be long & unguessable.
6466
- **$ngo_domain** The domain to use for validating users when not using white- or blacklists
6567
- **$ngo_whitelist** Optional list of authorized email addresses
6668
- **$ngo_blacklist** Optional list of unauthorized email addresses
@@ -70,6 +72,8 @@ variables are:
7072
- **$ngo_debug** If defined, will enable debug logging through nginx error logger
7173
- **$ngo_secure_cookies** If defined, will ensure that cookies can only be transfered over a secure connection
7274
- **$ngo_css** An optional stylesheet to replace the default stylesheet when using the body_filter
75+
- **$ngo_user** If set, will be populated with the OAuth username returned from Google (portion left of '@' in email)
76+
- **$ngo_email_as_user** If set and $ngo_user is defined, username returned will be full email address
7377

7478
## Configuring OAuth Access
7579

@@ -118,6 +122,7 @@ server {
118122
119123
set $ngo_client_id 'abc-def.apps.googleusercontent.com';
120124
set $ngo_client_secret 'abcdefg-123-xyz';
125+
set $ngo_token_secret 'a very long randomish string';
121126
access_by_lua_file "/etc/nginx/nginx-google-oauth/access.lua";
122127
123128
location / {
@@ -170,6 +175,36 @@ The filter operates by performing a regular expression match on ``<body>``,
170175
and so should act as a no-op for non-HTML content types. It may be necessary
171176
to use the body filter only on a subset of routes depending on your application.
172177

178+
## Username variable
179+
180+
If you wish to pass the username returned from Google to an external FastCGI/UWSGI script, consider using the ``$ngo_user`` variable:
181+
182+
```
183+
server {
184+
server_name supersecret.net;
185+
listen 443;
186+
187+
ssl on;
188+
ssl_certificate /etc/nginx/certs/supersecret.net.pem;
189+
ssl_certificate_key /etc/nginx/certs/supersecret.net.key;
190+
191+
set $ngo_client_id "abc-def.apps.googleusercontent.com";
192+
set $ngo_client_secret "abcdefg-123-xyz";
193+
set $ngo_token_secret "a very long randomish string";
194+
set $ngo_secure_cookies "true";
195+
access_by_lua_file "/etc/nginx/nginx-google-oauth/access.lua";
196+
197+
set $ngo_user "unknown@unknown.com";
198+
199+
include uwsgi_params;
200+
uwsgi_param REMOTE_USER $ngo_user;
201+
uwsgi_param AUTH_TYPE Basic;
202+
uwsgi_pass 127.0.0.1:3031;
203+
}
204+
```
205+
206+
If you wish the full email address returned from Google to be set as the username, set the ``$ngo_email_as_user`` variable to any non-empty value.
207+
173208
## Development
174209

175210
Bug reports and pull requests are [welcome](https://github.com/agoragames/nginx-google-oauth).

access.lua

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,76 @@
11

22
-- import requirements
3-
local cjson = require "cjson"
3+
4+
-- allow either cjson, or th-LuaJSON
5+
local has_cjson, jsonmod = pcall(require, "cjson")
6+
if not has_cjson then
7+
jsonmod = require "json"
8+
end
49

510
-- Ubuntu broke the install. Puts the source in /usr/share/lua/5.1/https.lua,
611
-- but since the source defines itself as the module "ssl.https", after we
7-
-- load the source, we need to grab the actual thing. Building from source
8-
-- wasn't practical.
9-
-- TODO: make this more generic but still work with Ubuntu
10-
require "https" --
12+
-- load the source, we need to grab the actual thing.
13+
pcall(require,"https")
1114
local https = require "ssl.https" -- /usr/share/lua/5.1/https.lua
1215
local ltn12 = require("ltn12")
1316

17+
local uri = ngx.var.uri
18+
local uri_args = ngx.req.get_uri_args()
19+
local scheme = ngx.var.scheme
20+
local server_name = ngx.var.server_name
21+
1422
-- setup some app-level vars
1523
local client_id = ngx.var.ngo_client_id
1624
local client_secret = ngx.var.ngo_client_secret
1725
local domain = ngx.var.ngo_domain
18-
local cb_scheme = ngx.var.ngo_callback_scheme or ngx.var.scheme
19-
local cb_server_name = ngx.var.ngo_callback_host or ngx.var.server_name
26+
local cb_scheme = ngx.var.ngo_callback_scheme or scheme
27+
local cb_server_name = ngx.var.ngo_callback_host or server_name
2028
local cb_uri = ngx.var.ngo_callback_uri or "/_oauth"
2129
local cb_url = cb_scheme.."://"..cb_server_name..cb_uri
30+
local redir_url = cb_scheme.."://"..cb_server_name..uri
2231
local signout_uri = ngx.var.ngo_signout_uri or "/_signout"
2332
local debug = ngx.var.ngo_debug
2433
local whitelist = ngx.var.ngo_whitelist
2534
local blacklist = ngx.var.ngo_blacklist
2635
local secure_cookies = ngx.var.ngo_secure_cookies
27-
28-
local uri_args = ngx.req.get_uri_args()
36+
local token_secret = ngx.var.ngo_token_secret or "UNSET"
37+
local set_user = ngx.var.ngo_user
38+
local email_as_user = ngx.var.ngo_email_as_user
39+
40+
-- Force the user to set a token secret
41+
if token_secret == "UNSET" then
42+
ngx.log(ngx.ERR, "$ngo_token_secret must be set in Nginx config!")
43+
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
44+
end
2945

3046
-- See https://developers.google.com/accounts/docs/OAuth2WebServer
31-
if ngx.var.uri == signout_uri then
47+
if uri == signout_uri then
3248
ngx.header["Set-Cookie"] = "AccessToken=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"
33-
return ngx.redirect(ngx.var.scheme.."://"..ngx.var.server_name)
49+
return ngx.redirect(cb_scheme.."://"..server_name)
3450
end
3551

36-
if not ngx.var.cookie_AccessToken then
52+
-- Enforce token security and expiration
53+
local oauth_expires = tonumber(ngx.var.cookie_OauthExpires) or 0
54+
local oauth_email = ngx.unescape_uri(ngx.var.cookie_OauthEmail or "")
55+
local oauth_access_token = ngx.unescape_uri(ngx.var.cookie_OauthAccessToken or "")
56+
local expected_token = ngx.encode_base64(ngx.hmac_sha1(token_secret, cb_server_name .. oauth_email .. oauth_expires))
57+
58+
if oauth_access_token == expected_token and oauth_expires and oauth_expires > ngx.time() then
59+
-- Populate the nginx 'ngo_user' variable with our Oauth username, if requested
60+
if set_user then
61+
local oauth_user, oauth_domain = oauth_email:match("([^@]+)@(.+)")
62+
if email_as_user then
63+
ngx.var.ngo_user = email
64+
else
65+
ngx.var.ngo_user = oauth_user
66+
end
67+
end
68+
return
69+
else
3770
-- If no access token and this isn't the callback URI, redirect to oauth
38-
if ngx.var.uri ~= cb_uri then
71+
if uri ~= cb_uri then
3972
-- Redirect to the /oauth endpoint, request access to ALL scopes
40-
return ngx.redirect("https://accounts.google.com/o/oauth2/auth?client_id="..client_id.."&scope=email&response_type=code&redirect_uri="..ngx.escape_uri(cb_url).."&state="..ngx.escape_uri(ngx.var.uri).."&login_hint="..ngx.escape_uri(domain))
73+
return ngx.redirect("https://accounts.google.com/o/oauth2/auth?client_id="..client_id.."&scope=email&response_type=code&redirect_uri="..ngx.escape_uri(cb_url).."&state="..ngx.escape_uri(redir_url).."&login_hint="..ngx.escape_uri(domain))
4174
end
4275

4376
-- Fetch teh authorization code from the parameters
@@ -72,8 +105,9 @@ if not ngx.var.cookie_AccessToken then
72105
end
73106

74107
-- use version 1 cookies so we don't have to encode. MSIE-old beware
75-
local json = cjson.decode( res )
108+
local json = jsonmod.decode( res )
76109
local access_token = json["access_token"]
110+
local expires = ngx.time() + json["expires_in"]
77111
local cookie_tail = ";version=1;path=/;Max-Age="..json["expires_in"]
78112
if secure_cookies then
79113
cookie_tail = cookie_tail..";secure"
@@ -100,15 +134,18 @@ if not ngx.var.cookie_AccessToken then
100134
ngx.log(ngx.ERR, "DEBUG: userinfo response "..res2..code2..status2..table.concat(result_table))
101135
end
102136

103-
json = cjson.decode( table.concat(result_table) )
137+
json = jsonmod.decode( table.concat(result_table) )
104138

105139
local name = json["name"]
106140
local email = json["email"]
107141
local picture = json["picture"]
142+
local token = ngx.encode_base64(ngx.hmac_sha1(token_secret, cb_server_name .. email .. expires))
143+
144+
local oauth_user, oauth_domain = email:match("([^@]+)@(.+)")
108145

109146
-- If no whitelist or blacklist, match on domain
110-
if not whitelist and not blacklist then
111-
if not string.find(email, "@"..domain) then
147+
if not whitelist and not blacklist and domain then
148+
if oauth_domain ~= domain then
112149
if debug then
113150
ngx.log(ngx.ERR, "DEBUG: "..email.." not in "..domain)
114151
end
@@ -117,7 +154,7 @@ if not ngx.var.cookie_AccessToken then
117154
end
118155

119156
if whitelist then
120-
if not string.find(whitelist, email) then
157+
if not string.find(" " .. whitelist .. " ", " " .. email .. " ") then
121158
if debug then
122159
ngx.log(ngx.ERR, "DEBUG: "..email.." not in whitelist")
123160
end
@@ -126,7 +163,7 @@ if not ngx.var.cookie_AccessToken then
126163
end
127164

128165
if blacklist then
129-
if string.find(blacklist, email) then
166+
if string.find(" " .. blacklist .. " ", " " .. email .. " ") then
130167
if debug then
131168
ngx.log(ngx.ERR, "DEBUG: "..email.." in blacklist")
132169
end
@@ -135,12 +172,22 @@ if not ngx.var.cookie_AccessToken then
135172
end
136173

137174
ngx.header["Set-Cookie"] = {
138-
"AccessToken="..access_token..cookie_tail,
139-
"Name="..ngx.escape_uri(name)..cookie_tail,
140-
"Email="..ngx.escape_uri(email)..cookie_tail,
141-
"Picture="..ngx.escape_uri(picture)..cookie_tail
175+
"OauthAccessToken="..ngx.escape_uri(token)..cookie_tail,
176+
"OauthExpires="..expires..cookie_tail,
177+
"OauthName="..ngx.escape_uri(name)..cookie_tail,
178+
"OauthEmail="..ngx.escape_uri(email)..cookie_tail,
179+
"OauthPicture="..ngx.escape_uri(picture)..cookie_tail
142180
}
143181

182+
-- Poplate our ngo_user variable
183+
if set_user then
184+
if email_as_user then
185+
ngx.var.ngo_user = email
186+
else
187+
ngx.var.ngo_user = oauth_user
188+
end
189+
end
190+
144191
-- Redirect
145192
if debug then
146193
ngx.log(ngx.ERR, "DEBUG: authorized "..json["email"]..", redirecting to "..uri_args["state"])

0 commit comments

Comments
 (0)