Skip to content
This repository was archived by the owner on Dec 12, 2018. It is now read-only.

Commit 5db9794

Browse files
authored
1073: Changed header semantics from X-Forwarded-Account to X-Forwarded-User (#1193)
1 parent 3865e15 commit 5db9794

File tree

7 files changed

+98
-51
lines changed

7 files changed

+98
-51
lines changed

docs/source/forwarded-request.rst

Lines changed: 78 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
Forwarded Request
44
=================
55

6-
If a user has authenticated, either by using a :ref:`login form <login>` or via :ref:`Request Authentication <request authentication>`,
7-
the user account's data will be forwarded to any backend origin servers behind the gateway in a request header.
6+
If a user has authenticated, either by using a :ref:`login form <login>` or via
7+
:ref:`Request Authentication <request authentication>`, the user account data will be forwarded to any backend
8+
origin servers behind the gateway in a request header.
89

9-
The origin server(s) may inspect this request header to discover information about the account. This information can
10+
The origin server(s) may inspect this request header to discover information about the user. This information can
1011
be used to customize views, send emails, update user-specific data, or practically anything else you can think of that
11-
might be user specific.
12+
might be user-specific.
1213

1314
This page covers how to customize everything related to this header and its contents.
1415

@@ -19,9 +20,9 @@ This page covers how to customize everything related to this header and its cont
1920
Forwarded Account Header Name
2021
-----------------------------
2122

22-
If a user ``Account`` is associated with a request that will be forwarded to an origin server, the account will be
23+
If a user account is associated with a request that will be forwarded to an origin server, the account will be
2324
converted to a String and added to the forwarded request as an HTTP request header. The default header name is
24-
``X-Forwarded-Account``.
25+
``X-Forwarded-User``, a de-facto HTTP header for forwarding user account information.
2526

2627
If you want to specify a different HTTP header name, set the ``stormpath.zuul.account.header.name`` configuration property:
2728

@@ -31,25 +32,25 @@ If you want to specify a different HTTP header name, set the ``stormpath.zuul.ac
3132
zuul:
3233
account:
3334
header:
34-
name: X-Forwarded-Account
35+
name: X-Forwarded-User
3536
3637
.. caution::
3738

3839
If you change this name, ensure the origin server(s) behind your gateway are updated to look for the new header name as well.
3940

4041
.. note::
4142

42-
If there is no account associated with the request, the header will not be present in the forwarded request at all.
43+
If there is no user account associated with the request, the header will not be present in the forwarded request at all.
4344

4445
Forwarded Account Header Value
4546
------------------------------
4647

47-
If an ``Account`` is associated with the request, the forwarded account header value will be a String that represents the
48+
If a user ``Account`` is associated with the request, the forwarded account header value will be a String that represents the
4849
account.
4950

50-
You can customize this String value to be anything you like - such as a simple single value like the Account's username
51-
or email, or the entire Account as a JSON document, or even a cryptographically-safe
52-
:ref:`JSON Web Token (JWT) <forwarded account header jwt>` that represents the Account information you choose.
51+
You can customize this String value to be anything you like - such as a simple single value like the account's username
52+
or email, or the entire account as a JSON document, or even a cryptographically-safe
53+
:ref:`JSON Web Token (JWT) <forwarded account header jwt>` that represents the account information you choose.
5354

5455
By default, a digitally-signed account :ref:`JWT <forwarded account header jwt>` will be used as the header value.
5556
When an origin server reads the forwarded header value, the origin server can verify the JWT signature. This allows
@@ -72,7 +73,7 @@ Single Account Field
7273
^^^^^^^^^^^^^^^^^^^^
7374

7475
If you do not want or need the security guarantees of a JWT and want your header value to be a single string value,
75-
like the account's username or email, you can set the following configuration:
76+
such as the account's username or email, you can set the following configuration:
7677

7778
.. code-block:: yaml
7879
@@ -98,7 +99,7 @@ the origin server(s) would look like this:
9899

99100
.. code-block:: properties
100101
101-
x-forwarded-account: tk421
102+
x-forwarded-user: tk421
102103
103104
A similar example using the account email instead is shown in the :ref:`field <object conversion field>` section.
104105

@@ -426,7 +427,7 @@ the header sent to the origin server(s) would look like this:
426427

427428
.. code-block:: properties
428429
429-
x-forwarded-account: tk421@galacticempire.com
430+
x-forwarded-user: tk421@galacticempire.com
430431
431432
.. _object conversion fields:
432433

@@ -1023,7 +1024,7 @@ The ``valueClaim`` config properties allow you to control how the :ref:`Account
10231024
represented inside the JWT.
10241025

10251026
By default, the :ref:`Account JSON <forwarded account json>` is represented under a single JWT claim named
1026-
``account``. This results in JWT claims that look something like this:
1027+
``user``. This results in JWT claims that look something like this:
10271028

10281029
.. code-block:: javascript
10291030
:emphasize-lines: 5-12
@@ -1032,7 +1033,7 @@ By default, the :ref:`Account JSON <forwarded account json>` is represented unde
10321033
"iat": 1482972605,
10331034
"iss": "my gateway",
10341035
"aud": "my origin server",
1035-
"account": {
1036+
"user": {
10361037
"username": "tk421",
10371038
"email": "tk421@galacticempire.com",
10381039
"givenName": "TK421",
@@ -1044,24 +1045,24 @@ By default, the :ref:`Account JSON <forwarded account json>` is represented unde
10441045
}
10451046
10461047
1047-
As you can see, the account JSON is reflected as a single ``account`` claim, and the entire account can be
1048-
retrieved by a single lookup of that claim. This helps keep your account information 'clean' and separate from other
1049-
JWT claims like ``iat``, ``iss``, ``aud``, etc.
1048+
As you can see, the user account JSON is reflected as a single ``user`` claim, and the entire user account can be
1049+
retrieved by a single lookup of that claim. This helps keep your user account information 'clean' and separate from
1050+
other JWT claims like ``iat``, ``iss``, ``aud``, etc.
10501051

10511052
If you prefer, you can :ref:`change the claim name <forwarded account jwt valueclaim name>` or
10521053
:ref:`not use a claim at all <forwarded account jwt valueclaim enabled>`
10531054
via the respective nested ``name`` and ``enabled`` properties.
10541055

10551056
.. tip::
10561057

1057-
For you JWT experts out there, you might want to know why we didn't represent the account with the
1058+
For you JWT experts out there, you might want to know why we didn't represent the user account with the
10581059
`JWT sub claim <https://tools.ietf.org/html/rfc7519#section-4.1.2>`_ . The ``sub`` claim is the RFC-standard claim
1059-
that defines the target identity of the JWT, and the account is the identity we care about, right? So why didn't we
1060-
just use the default ``sub`` claim instead of ``account``?
1060+
that defines the target identity of the JWT, and the user account is the identity we care about, right? So why
1061+
didn't we just use the default ``sub`` claim instead of ``user``?
10611062

10621063
The reason is that the JWT RFC (`RFC 7519 <https://tools.ietf.org/html/rfc7519>`_) says that the value of the ``sub``
10631064
claim must be a ``StringOrURI`` data type value, as defined in
1064-
`RFC 7519 section 2 (Terminology) <https://tools.ietf.org/html/rfc7519#section-2>`_. The Account JSON is a full
1065+
`RFC 7519 section 2 (Terminology) <https://tools.ietf.org/html/rfc7519#section-2>`_. The user account JSON is a full
10651066
JSON object structure, which is neither a String nor a URI as required by the RFC. So, we choose a different
10661067
claim name to avoid any parsing/validation errors that JWT libraries might enforce for that claim, and all is well.
10671068

@@ -1071,7 +1072,7 @@ via the respective nested ``name`` and ``enabled`` properties.
10711072
``enabled``
10721073
"""""""""""
10731074

1074-
The :ref:`Account JSON <forwarded account json>` is nested in the JWT claims as single claim named ``account`` by
1075+
The :ref:`Account JSON <forwarded account json>` is nested in the JWT claims as single claim named ``user`` by
10751076
default.
10761077

10771078
If you don't want to use a specific value claim at all, and instead prefer to have the account properties mixed
@@ -1089,7 +1090,7 @@ entirely by setting ``stormpath.zuul.account.header.jwt.valueClaim.enabled`` to
10891090
enabled: false
10901091
10911092
1092-
After setting this property to ``false``, all account JSON name/value pairs are added directly to the JWT claims,
1093+
After setting this property to ``false``, all user account JSON name/value pairs are added directly to the JWT claims,
10931094
making each account property a claim itself. The account properties and any other JWT-related ones are all
10941095
intermixed and 'just claims' as far as the JWT is concerned. For example:
10951096

@@ -1114,7 +1115,7 @@ intermixed and 'just claims' as far as the JWT is concerned. For example:
11141115
``name``
11151116
""""""""
11161117

1117-
The single value claim is named ``account`` by default. You can change this name if you prefer by setting the
1118+
The single value claim is named ``user`` by default. You can change this name if you prefer by setting the
11181119
``stormpath.zuul.account.header.jwt.valueClaim.name`` config property. For example:
11191120

11201121
.. code-block:: yaml
@@ -1125,7 +1126,7 @@ The single value claim is named ``account`` by default. You can change this nam
11251126
header:
11261127
jwt:
11271128
valueClaim:
1128-
name: user
1129+
name: userAccount
11291130
11301131
This would result in JWT claims that look something like this:
11311132

@@ -1136,7 +1137,7 @@ This would result in JWT claims that look something like this:
11361137
"iat": 1482972605,
11371138
"iss": "my gateway",
11381139
"aud": "my origin server",
1139-
"user": {
1140+
"userAccount": {
11401141
"username": "tk421",
11411142
"email": "tk421@galacticempire.com",
11421143
"givenName": "TK421",
@@ -1173,21 +1174,61 @@ Custom Header Value
11731174
-------------------
11741175

11751176
Finally, if *none* of the above options are sufficient for you, don't worry, we still have you covered. You can still
1176-
create any string you want as the header value with a little custom code. You have two easy options:
1177+
create any string you want as the header value with a little custom code. You have three easy options:
1178+
1179+
1. If you don't need access to the HttpServletRequest/Response pair and want to convert the Account to a
1180+
Map that will be automatically turned into JSON or a JWT for you, you can define your own
1181+
:ref:`account-to-map conversion function <forwarded account map function>` bean.
11771182

1178-
1. If you don't need access to the HttpServletRequest/Response pair and just want to convert an Account
1179-
object to a String, you can define your own
1183+
2. If you don't need access to the HttpServletRequest/Response pair and want to do the full account to final
1184+
header String conversion logic yourself, you can define your own
11801185
:ref:`account-to-string conversion function <forwarded account to string function>` bean.
11811186

1182-
2. If you need access to the HttpServletRequest/Response during the account-to-string conversion process, you can
1187+
3. If you need access to the HttpServletRequest/Response during the account-to-string conversion process, you can
11831188
define your own :ref:`stormpathForwardedAccountHeaderValueResolver` bean.
11841189

1185-
In either case you will need to add the proper bean in your gateway Spring config.
1190+
In any case you will need to add the proper bean in your gateway Spring config.
11861191

11871192
.. note::
11881193

1189-
Remember that adding or changing either bean will probably require changes to your origin server(s) - the origin
1190-
server(s) will need to understand how to read the different Account string value created by your conversion bean.
1194+
Remember that adding or changing any of these beans will probably require changes to your origin server(s) -
1195+
the origin server(s) will need to understand how to read the final Account string value created by your
1196+
conversion bean.
1197+
1198+
1199+
.. _forwarded account map function:
1200+
1201+
Account-to-Map Function
1202+
^^^^^^^^^^^^^^^^^^^^^^^
1203+
1204+
If you don't need access to the HttpServletRequest/Response pair, and you just want to be able to convert an ``Account``
1205+
instance to a ``Map<String,?>``, you can define your own ``stormpathForwardedAccountMapFunction`` bean:
1206+
1207+
.. code-block:: java
1208+
1209+
@Bean
1210+
public Function<Account, ?> stormpathForwardedAccountMapFunction() {
1211+
return new MyAccountToMapFunction(); //implement me
1212+
}
1213+
1214+
1215+
This bean/method must be named ``stormpathForwardedAccountMapFunction`` and the bean must implement the
1216+
``com.stormpath.sdk.lang.Function<Account,?>`` interface.
1217+
1218+
When the gateway determines that there is an account to forward to an origin server, your custom function will be
1219+
called with an ``Account`` instance and it will return a ``Map<String,?>`` result.
1220+
1221+
This resulting map will be
1222+
converted to a JSON document automatically, and then potentially converted to a JWT depending on the value of the
1223+
:ref:`stormpath.zuul.account.header.jwt.enabled <forwarded account header jwt enabled>` property (which is enabled by
1224+
default). If JWT is enabled, you can :ref:`customize the JWT as documented <forwarded account header jwt>` above.
1225+
1226+
The final resulting JSON or JWT string will be the header value.
1227+
1228+
.. note::
1229+
1230+
If the resulting Map is ``null`` or empty, the header will not be present in the forwarded request at all.
1231+
11911232

11921233
.. _forwarded account to string function:
11931234

@@ -1204,7 +1245,7 @@ instance to a String, you can define your own ``stormpathForwardedAccountStringF
12041245
return new MyAccountToStringFunction(); //implement me
12051246
}
12061247
1207-
This bean/method must be named ``stormpathForwardedAccountStringFunction``. The bean must implement the
1248+
This bean/method must be named ``stormpathForwardedAccountStringFunction`` and the bean must implement the
12081249
``com.stormpath.sdk.lang.Function<Account,String>`` interface.
12091250

12101251
When the gateway determines that there is an account to forward to an origin server, your custom function will be

extensions/spring/cloud/stormpath-zuul-spring-cloud-starter/src/main/java/com/stormpath/spring/cloud/zuul/autoconfigure/StormpathZuulAutoConfiguration.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ protected static SignatureAlgorithm getAlgorithm(byte[] hmacSigningKeyBytes) {
242242
}
243243
if (!signatureAlgorithm.isHmac()) {
244244
String msg = "Unable to use specified JWT signature algorithm '" + signatureAlgorithm + "' when " +
245-
"creating X-Forwarded-Account JWTs, as this algorithm is incompatible with the " +
245+
"creating X-Forwarded-User JWTs, as this algorithm is incompatible with the " +
246246
"fallback/default Stormpath Client ApiKey secret signing key. Defaulting to '" +
247247
defaultSigAlg + "'. To avoid this message, either 1) do not specify a signature algorithm to " +
248248
"let the framework choose an algorithm appropriate for the default signing key, or 2) define " +
@@ -291,6 +291,10 @@ public Function<Account, String> stormpathForwardedAccountStringFunction() {
291291
public String apply(Account account) {
292292
Object value = accountFunction.apply(account);
293293

294+
if (value == null || (value instanceof Map && Collections.isEmpty((Map)value))) {
295+
return null;
296+
}
297+
294298
if (value instanceof String) {
295299
return (String)value;
296300
}

extensions/spring/cloud/stormpath-zuul-spring-cloud-starter/src/main/java/com/stormpath/spring/cloud/zuul/config/StormpathZuulAccountHeaderConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
public class StormpathZuulAccountHeaderConfig {
2727

2828
@SuppressWarnings("WeakerAccess")
29-
public static final String DEFAULT_NAME = "X-Forwarded-Account";
29+
public static final String DEFAULT_NAME = "X-Forwarded-User";
3030

3131
private String name;
3232

extensions/spring/cloud/stormpath-zuul-spring-cloud-starter/src/main/java/com/stormpath/spring/cloud/zuul/config/ValueClaimConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
*/
2121
public class ValueClaimConfig {
2222

23+
public static final String DEFAULT_CLAIM_NAME = "user";
24+
2325
private boolean enabled;
2426

2527
private String name;
2628

2729
public ValueClaimConfig() {
2830
this.enabled = true;
29-
this.name = "account";
31+
this.name = DEFAULT_CLAIM_NAME;
3032
}
3133

3234
public boolean isEnabled() {

extensions/spring/cloud/stormpath-zuul-spring-cloud-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@
4141
{
4242
"name": "stormpath.zuul.account.header.name",
4343
"type": "java.lang.Integer",
44-
"description": "The name of the HTTP header used in a forwarded request that contains a String representing the Account associated with the inbound request. If there is no account associated with the request, this header will not be set. Unless overridden, the default value is X-Forwarded-Account",
45-
"defaultValue": "X-Forwarded-Account"
44+
"description": "The name of the HTTP header used in a forwarded request that contains a String representing the Account associated with the inbound request. If there is no account associated with the request, this header will not be set. Unless overridden, the default value is X-Forwarded-User",
45+
"defaultValue": "X-Forwarded-User"
4646
},
4747
{
4848
"name": "stormpath.zuul.account.header.value",
4949
"type": "com.stormpath.sdk.convert.Conversion",
50-
"description": "The conversion rules to apply to any discovered Account to be forwarded. These rules produce an account String that will be used as the value of the X-Forwarded-Account header."
50+
"description": "The conversion rules to apply to any discovered Account to be forwarded. These rules produce an account String that will be used as the value of the X-Forwarded-User header."
5151
},
5252
{
5353
"name": "stormpath.zuul.account.header.jwt.enabled",
5454
"type": "java.lang.Boolean",
55-
"description": "Whether or not the X-Forwarded-Account header value should be a JWT instead of a plaintext string. Unless overridden, the default value is true for extra security guarantees. A false value will result in a plaintext string header value.",
55+
"description": "Whether or not the X-Forwarded-User header value should be a JWT instead of a plaintext string. Unless overridden, the default value is true for extra security guarantees. A false value will result in a plaintext string header value.",
5656
"defaultValue": true
5757
},
5858
{
@@ -86,8 +86,8 @@
8686
{
8787
"name": "stormpath.zuul.account.header.jwt.valueClaim.name",
8888
"type": "java.lang.String",
89-
"description": "The name of the claim within the forwarded account JWT claims map that represents the account object. Unless overridden, the default value is 'account'. This property is only evaluated if stormpath.zuul.account.header.jwt.valueClaim.enabled is equal to true.",
90-
"defaultValue": "account"
89+
"description": "The name of the claim within the forwarded user JWT claims map that represents the user account object. Unless overridden, the default value is 'user'. This property is only evaluated if stormpath.zuul.account.header.jwt.valueClaim.enabled is equal to true.",
90+
"defaultValue": "user"
9191
},
9292
{
9393
"name": "stormpath.zuul.account.header.jwt.key.alg",
@@ -114,7 +114,7 @@
114114
{
115115
"name": "stormpath.zuul.account.header.jwt.key.kid",
116116
"type": "java.lang.Boolean",
117-
"description": "Specifies the identifier of the signing key used to digitally sign the forwarded account JWT. This is useful because backend origin servers behind the gateway can inspect the X-Forwarded-Account header JWT, find this key id, and based on this id, look up the appropriate key that should be used to verify the JWT's digital signature. If you specify a signing key (and you should!) you would almost always want to set this property. If you do not specify a signing key or this property, the Stormpath Client API Key secret will be used as the HMAC signing key, and this property ('kid') will default to the Client API Key's HREF URL."
117+
"description": "Specifies the identifier of the signing key used to digitally sign the forwarded account JWT. This is useful because backend origin servers behind the gateway can inspect the X-Forwarded-User header JWT, find this key id, and based on this id, look up the appropriate key that should be used to verify the JWT's digital signature. If you specify a signing key (and you should!) you would almost always want to set this property. If you do not specify a signing key or this property, the Stormpath Client API Key secret will be used as the HMAC signing key, and this property ('kid') will default to the Client API Key's HREF URL."
118118
}
119119
],
120120
"hints": [

0 commit comments

Comments
 (0)