Skip to content

Commit 5083ad6

Browse files
feat: auth plugins respond with www-authenticate header with realm (#12864)
1 parent fdfca68 commit 5083ad6

File tree

16 files changed

+858
-6
lines changed

16 files changed

+858
-6
lines changed

apisix/plugins/basic-auth.lua

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ local schema = {
3232
hide_credentials = {
3333
type = "boolean",
3434
default = false,
35-
}
35+
},
36+
realm = schema_def.get_realm_schema("basic"),
3637
},
3738
anonymous_consumer = schema_def.anonymous_consumer_schema,
3839
}
@@ -124,7 +125,6 @@ end
124125
local function find_consumer(ctx)
125126
local auth_header = core.request.header(ctx, "Authorization")
126127
if not auth_header then
127-
core.response.set_header("WWW-Authenticate", "Basic realm='.'")
128128
return nil, nil, "Missing authorization in request"
129129
end
130130

@@ -157,15 +157,17 @@ end
157157

158158

159159
function _M.rewrite(conf, ctx)
160-
local cur_consumer, consumer_conf, err = find_consumer(ctx)
160+
local cur_consumer, consumer_conf, err = find_consumer(ctx, conf)
161161
if not cur_consumer then
162162
if not conf.anonymous_consumer then
163+
core.response.set_header("WWW-Authenticate", "Basic realm=\"" .. conf.realm .. "\"")
163164
return 401, { message = err }
164165
end
165166
cur_consumer, consumer_conf, err = consumer.get_anonymous_consumer(conf.anonymous_consumer)
166167
if not cur_consumer then
167168
err = "basic-auth failed to authenticate the request, code: 401. error: " .. err
168169
core.log.error(err)
170+
core.response.set_header("WWW-Authenticate", "Basic realm=\"" .. conf.realm .. "\"")
169171
return 401, { message = "Invalid user authorization" }
170172
end
171173
end

apisix/plugins/hmac-auth.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ local schema = {
6363
default = false,
6464
},
6565
hide_credentials = {type = "boolean", default = false},
66+
realm = schema_def.get_realm_schema("hmac"),
6667
anonymous_consumer = schema_def.anonymous_consumer_schema,
6768
},
6869
}
@@ -346,14 +347,17 @@ function _M.rewrite(conf, ctx)
346347
local cur_consumer, consumers_conf, err = find_consumer(conf, ctx)
347348
if not cur_consumer then
348349
if not conf.anonymous_consumer then
350+
core.response.set_header("WWW-Authenticate", "hmac realm=\"" .. conf.realm .. "\"")
349351
return 401, { message = err }
350352
end
351353
cur_consumer, consumers_conf, err = consumer.get_anonymous_consumer(conf.anonymous_consumer)
352354
if not cur_consumer then
353355
if auth_utils.is_running_under_multi_auth(ctx) then
356+
core.response.set_header("WWW-Authenticate", "hmac realm=\"" .. conf.realm .. "\"")
354357
return 401, err
355358
end
356359
core.log.error(err)
360+
core.response.set_header("WWW-Authenticate", "hmac realm=\"" .. conf.realm .. "\"")
357361
return 401, { message = "Invalid user authorization" }
358362
end
359363
end

apisix/plugins/jwt-auth.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ local schema = {
5858
type = "boolean",
5959
default = false
6060
},
61+
realm = schema_def.get_realm_schema("jwt"),
6162
anonymous_consumer = schema_def.anonymous_consumer_schema,
6263
},
6364
}
@@ -307,12 +308,14 @@ function _M.rewrite(conf, ctx)
307308
local consumer, consumer_conf, err = find_consumer(conf, ctx)
308309
if not consumer then
309310
if not conf.anonymous_consumer then
311+
core.response.set_header("WWW-Authenticate", "Bearer realm=\"" .. conf.realm .. "\"")
310312
return 401, { message = err }
311313
end
312314
consumer, consumer_conf, err = consumer_mod.get_anonymous_consumer(conf.anonymous_consumer)
313315
if not consumer then
314316
err = "jwt-auth failed to authenticate the request, code: 401. error: " .. err
315317
core.log.error(err)
318+
core.response.set_header("WWW-Authenticate", "Bearer realm=\"" .. conf.realm .. "\"")
316319
return 401, { message = "Invalid user authorization"}
317320
end
318321
end

apisix/plugins/key-auth.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ local schema = {
3030
type = "string",
3131
default = "apikey",
3232
},
33+
realm = schema_def.get_realm_schema("key"),
3334
hide_credentials = {
3435
type = "boolean",
3536
default = false,
@@ -104,12 +105,14 @@ function _M.rewrite(conf, ctx)
104105
local consumer, consumer_conf, err = find_consumer(ctx, conf)
105106
if not consumer then
106107
if not conf.anonymous_consumer then
108+
core.response.set_header("WWW-Authenticate", "apikey realm=\"" .. conf.realm .. "\"")
107109
return 401, { message = err}
108110
end
109111
consumer, consumer_conf, err = consumer_mod.get_anonymous_consumer(conf.anonymous_consumer)
110112
if not consumer then
111113
err = "key-auth failed to authenticate the request, code: 401. error: " .. err
112114
core.log.error(err)
115+
core.response.set_header("WWW-Authenticate", "apikey realm=\"" .. conf.realm .. "\"")
113116
return 401, { message = "Invalid user authorization"}
114117
end
115118
end

apisix/plugins/ldap-auth.lua

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ local ngx = ngx
1919
local ngx_re = require("ngx.re")
2020
local consumer_mod = require("apisix.consumer")
2121
local ldap = require("resty.ldap")
22+
local schema_def = require("apisix.schema_def")
23+
2224

2325
local schema = {
2426
type = "object",
@@ -28,7 +30,8 @@ local schema = {
2830
ldap_uri = { type = "string" },
2931
use_tls = { type = "boolean", default = false },
3032
tls_verify = { type = "boolean", default = false },
31-
uid = { type = "string", default = "cn" }
33+
uid = { type = "string", default = "cn" },
34+
realm = schema_def.get_realm_schema("ldap"),
3235
},
3336
required = {"base_dn","ldap_uri"},
3437
}
@@ -106,7 +109,7 @@ function _M.rewrite(conf, ctx)
106109
-- 1. extract authorization from header
107110
local auth_header = core.request.header(ctx, "Authorization")
108111
if not auth_header then
109-
core.response.set_header("WWW-Authenticate", "Basic realm='.'")
112+
core.response.set_header("WWW-Authenticate", "Basic realm=\"" .. conf.realm .. "\"")
110113
return 401, { message = "Missing authorization in request" }
111114
end
112115

@@ -117,6 +120,7 @@ function _M.rewrite(conf, ctx)
117120
else
118121
core.log.warn("nil user")
119122
end
123+
core.response.set_header("WWW-Authenticate", "Basic realm=\"" .. conf.realm .. "\"")
120124
return 401, { message = "Invalid authorization in request" }
121125
end
122126

@@ -136,6 +140,7 @@ function _M.rewrite(conf, ctx)
136140
local res, err = ldap.ldap_authenticate(user.username, user.password, ldapconf)
137141
if not res then
138142
core.log.warn("ldap-auth failed: ", err)
143+
core.response.set_header("WWW-Authenticate", "Basic realm=\"" .. conf.realm .. "\"")
139144
return 401, { message = "Invalid user authorization" }
140145
end
141146

@@ -144,12 +149,14 @@ function _M.rewrite(conf, ctx)
144149
-- 3. Retrieve consumer for authorization plugin
145150
local consumer_conf = consumer_mod.plugin(plugin_name)
146151
if not consumer_conf then
152+
core.response.set_header("WWW-Authenticate", "Basic realm=\"" .. conf.realm .. "\"")
147153
return 401, { message = "Missing related consumer" }
148154
end
149155

150156
local consumers = consumer_mod.consumers_kv(plugin_name, consumer_conf, "user_dn")
151157
local consumer = consumers[user_dn]
152158
if not consumer then
159+
core.response.set_header("WWW-Authenticate", "Basic realm=\"" .. conf.realm .. "\"")
153160
return 401, {message = "Invalid user authorization"}
154161
end
155162
consumer_mod.attach_consumer(ctx, consumer, consumer_conf)

apisix/schema_def.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ _M.anonymous_consumer_schema = {
2929
minLength = "1"
3030
}
3131

32+
function _M.get_realm_schema(default_val)
33+
return {
34+
type = "string",
35+
-- Pattern: Only allow printable ASCII, but EXCLUDE " and \
36+
-- \x20-\x21 (Space and !)
37+
-- \x23-\x5B (# through [)
38+
-- \x5D-\x7E (] through ~)
39+
-- Escaped closing bracket (\x5D) assertion for PCRE compatibility
40+
pattern = "^[\x20-\x21\x23-\x5B\\]-\x7E]+$",
41+
default = default_val,
42+
minLength = 1,
43+
maxLength = 128,
44+
}
45+
end
46+
3247
local id_schema = {
3348
anyOf = {
3449
{

docs/en/latest/plugins/basic-auth.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ For Route:
5555
|------------------|---------|----------|---------|------------------------------------------------------------------------|
5656
| hide_credentials | boolean | False | false | If true, do not pass the authorization request header to Upstream services. |
5757
| anonymous_consumer | boolean | False | false | Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication. |
58+
| realm | string | False | basic | The realm to include in the `WWW-Authenticate` header when authentication fails. |
5859

5960
## Examples
6061

docs/en/latest/plugins/hmac-auth.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ The following attributes are available for configurations on Routes or Services.
5454
| allowed_algorithms | array[string] | False | ["hmac-sha1","hmac-sha256","hmac-sha512"] | combination of "hmac-sha1","hmac-sha256",and "hmac-sha512" | The list of HMAC algorithms allowed. |
5555
| clock_skew | integer | False | 300 | >=1 | Maximum allowable time difference in seconds between the client request's timestamp and APISIX server's current time. This helps account for discrepancies in time synchronization between the client’s and server’s clocks and protect against replay attacks. The timestamp in the Date header (must be in GMT format) will be used for the calculation. |
5656
| signed_headers | array[string] | False | | | The list of HMAC-signed headers that should be included in the client request's HMAC signature. |
57-
| validate_request_body | boolean | False | false | | If true, validate the integrity of the request body to ensure it has not been tampered with during transmission. Specifically, the Plugin creates a SHA-256 base64-encoded digest and compare it to the `Digest` header. If the Digest` header is missing or if the digests do not match, the validation fails. |
57+
| validate_request_body | boolean | False | false | | If true, validate the integrity of the request body to ensure it has not been tampered with during transmission. Specifically, the Plugin creates a SHA-256 base64-encoded digest and compare it to the `Digest` header. If the `Digest` header is missing or if the digests do not match, the validation fails. |
5858
| hide_credentials | boolean | False | false | | If true, do not pass the authorization request header to Upstream services. |
5959
| anonymous_consumer | string | False | | | Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication. |
60+
| realm | string | False | hmac | | The realm to include in the `WWW-Authenticate` header when authentication fails. |
6061

6162
NOTE: `encrypt_fields = {"secret_key"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
6263

docs/en/latest/plugins/jwt-auth.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ For Routes or Services:
6868
| key_claim_name | string | False | key | The name of the JWT claim that contains the user key (corresponds to Consumer's key attribute). |
6969
| anonymous_consumer | string | False | false | Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication. |
7070
| store_in_ctx | boolean | False | false | Set to true will store the JWT payload in the request context (`ctx.jwt_auth_payload`). This allows lower-priority plugins that run afterwards on the same request to retrieve and use the JWT token. |
71+
| realm | string | False | jwt | The realm to include in the `WWW-Authenticate` header when authentication fails. |
7172

7273
You can implement `jwt-auth` with [HashiCorp Vault](https://www.vaultproject.io/) to store and fetch secrets and RSA keys pairs from its [encrypted KV engine](https://developer.hashicorp.com/vault/docs/secrets/kv) using the [APISIX Secret](../terminology/secret.md) resource.
7374

docs/en/latest/plugins/key-auth.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ For Route:
5858
| query | string | False | apikey | The query string to get the key from. Lower priority than header. |
5959
| hide_credentials | boolean | False | false | If true, do not pass the header or query string with key to Upstream services. |
6060
| anonymous_consumer | string | False | false | Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication. |
61+
| realm | string | False | key | The realm to include in the `WWW-Authenticate` header when authentication fails. |
6162

6263
## Examples
6364

0 commit comments

Comments
 (0)