Skip to content
This repository was archived by the owner on Dec 12, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 78 additions & 37 deletions docs/source/forwarded-request.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
Forwarded Request
=================

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

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

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

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

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

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

Expand All @@ -31,25 +32,25 @@ If you want to specify a different HTTP header name, set the ``stormpath.zuul.ac
zuul:
account:
header:
name: X-Forwarded-Account
name: X-Forwarded-User

.. caution::

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

.. note::

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

Forwarded Account Header Value
------------------------------

If an ``Account`` is associated with the request, the forwarded account header value will be a String that represents the
If a user ``Account`` is associated with the request, the forwarded account header value will be a String that represents the
account.

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

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

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

.. code-block:: yaml

Expand All @@ -98,7 +99,7 @@ the origin server(s) would look like this:

.. code-block:: properties

x-forwarded-account: tk421
x-forwarded-user: tk421

A similar example using the account email instead is shown in the :ref:`field <object conversion field>` section.

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

.. code-block:: properties

x-forwarded-account: tk421@galacticempire.com
x-forwarded-user: tk421@galacticempire.com

.. _object conversion fields:

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

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

.. code-block:: javascript
:emphasize-lines: 5-12
Expand All @@ -1032,7 +1033,7 @@ By default, the :ref:`Account JSON <forwarded account json>` is represented unde
"iat": 1482972605,
"iss": "my gateway",
"aud": "my origin server",
"account": {
"user": {
"username": "tk421",
"email": "tk421@galacticempire.com",
"givenName": "TK421",
Expand All @@ -1044,24 +1045,24 @@ By default, the :ref:`Account JSON <forwarded account json>` is represented unde
}


As you can see, the account JSON is reflected as a single ``account`` claim, and the entire account can be
retrieved by a single lookup of that claim. This helps keep your account information 'clean' and separate from other
JWT claims like ``iat``, ``iss``, ``aud``, etc.
As you can see, the user account JSON is reflected as a single ``user`` claim, and the entire user account can be
retrieved by a single lookup of that claim. This helps keep your user account information 'clean' and separate from
other JWT claims like ``iat``, ``iss``, ``aud``, etc.

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

.. tip::

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

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

Expand All @@ -1071,7 +1072,7 @@ via the respective nested ``name`` and ``enabled`` properties.
``enabled``
"""""""""""

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

If you don't want to use a specific value claim at all, and instead prefer to have the account properties mixed
Expand All @@ -1089,7 +1090,7 @@ entirely by setting ``stormpath.zuul.account.header.jwt.valueClaim.enabled`` to
enabled: false


After setting this property to ``false``, all account JSON name/value pairs are added directly to the JWT claims,
After setting this property to ``false``, all user account JSON name/value pairs are added directly to the JWT claims,
making each account property a claim itself. The account properties and any other JWT-related ones are all
intermixed and 'just claims' as far as the JWT is concerned. For example:

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

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

.. code-block:: yaml
Expand All @@ -1125,7 +1126,7 @@ The single value claim is named ``account`` by default. You can change this nam
header:
jwt:
valueClaim:
name: user
name: userAccount

This would result in JWT claims that look something like this:

Expand All @@ -1136,7 +1137,7 @@ This would result in JWT claims that look something like this:
"iat": 1482972605,
"iss": "my gateway",
"aud": "my origin server",
"user": {
"userAccount": {
"username": "tk421",
"email": "tk421@galacticempire.com",
"givenName": "TK421",
Expand Down Expand Up @@ -1173,21 +1174,61 @@ Custom Header Value
-------------------

Finally, if *none* of the above options are sufficient for you, don't worry, we still have you covered. You can still
create any string you want as the header value with a little custom code. You have two easy options:
create any string you want as the header value with a little custom code. You have three easy options:

1. If you don't need access to the HttpServletRequest/Response pair and want to convert the Account to a
Map that will be automatically turned into JSON or a JWT for you, you can define your own
:ref:`account-to-map conversion function <forwarded account map function>` bean.

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

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

In either case you will need to add the proper bean in your gateway Spring config.
In any case you will need to add the proper bean in your gateway Spring config.

.. note::

Remember that adding or changing either bean will probably require changes to your origin server(s) - the origin
server(s) will need to understand how to read the different Account string value created by your conversion bean.
Remember that adding or changing any of these beans will probably require changes to your origin server(s) -
the origin server(s) will need to understand how to read the final Account string value created by your
conversion bean.


.. _forwarded account map function:

Account-to-Map Function
^^^^^^^^^^^^^^^^^^^^^^^

If you don't need access to the HttpServletRequest/Response pair, and you just want to be able to convert an ``Account``
instance to a ``Map<String,?>``, you can define your own ``stormpathForwardedAccountMapFunction`` bean:

.. code-block:: java

@Bean
public Function<Account, ?> stormpathForwardedAccountMapFunction() {
return new MyAccountToMapFunction(); //implement me
}


This bean/method must be named ``stormpathForwardedAccountMapFunction`` and the bean must implement the
``com.stormpath.sdk.lang.Function<Account,?>`` interface.

When the gateway determines that there is an account to forward to an origin server, your custom function will be
called with an ``Account`` instance and it will return a ``Map<String,?>`` result.

This resulting map will be
converted to a JSON document automatically, and then potentially converted to a JWT depending on the value of the
:ref:`stormpath.zuul.account.header.jwt.enabled <forwarded account header jwt enabled>` property (which is enabled by
default). If JWT is enabled, you can :ref:`customize the JWT as documented <forwarded account header jwt>` above.

The final resulting JSON or JWT string will be the header value.

.. note::

If the resulting Map is ``null`` or empty, the header will not be present in the forwarded request at all.


.. _forwarded account to string function:

Expand All @@ -1204,7 +1245,7 @@ instance to a String, you can define your own ``stormpathForwardedAccountStringF
return new MyAccountToStringFunction(); //implement me
}

This bean/method must be named ``stormpathForwardedAccountStringFunction``. The bean must implement the
This bean/method must be named ``stormpathForwardedAccountStringFunction`` and the bean must implement the
``com.stormpath.sdk.lang.Function<Account,String>`` interface.

When the gateway determines that there is an account to forward to an origin server, your custom function will be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ protected static SignatureAlgorithm getAlgorithm(byte[] hmacSigningKeyBytes) {
}
if (!signatureAlgorithm.isHmac()) {
String msg = "Unable to use specified JWT signature algorithm '" + signatureAlgorithm + "' when " +
"creating X-Forwarded-Account JWTs, as this algorithm is incompatible with the " +
"creating X-Forwarded-User JWTs, as this algorithm is incompatible with the " +
"fallback/default Stormpath Client ApiKey secret signing key. Defaulting to '" +
defaultSigAlg + "'. To avoid this message, either 1) do not specify a signature algorithm to " +
"let the framework choose an algorithm appropriate for the default signing key, or 2) define " +
Expand Down Expand Up @@ -291,6 +291,10 @@ public Function<Account, String> stormpathForwardedAccountStringFunction() {
public String apply(Account account) {
Object value = accountFunction.apply(account);

if (value == null || (value instanceof Map && Collections.isEmpty((Map)value))) {
return null;
}

if (value instanceof String) {
return (String)value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
public class StormpathZuulAccountHeaderConfig {

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

private String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
*/
public class ValueClaimConfig {

public static final String DEFAULT_CLAIM_NAME = "user";

private boolean enabled;

private String name;

public ValueClaimConfig() {
this.enabled = true;
this.name = "account";
this.name = DEFAULT_CLAIM_NAME;
}

public boolean isEnabled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@
{
"name": "stormpath.zuul.account.header.name",
"type": "java.lang.Integer",
"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",
"defaultValue": "X-Forwarded-Account"
"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",
"defaultValue": "X-Forwarded-User"
},
{
"name": "stormpath.zuul.account.header.value",
"type": "com.stormpath.sdk.convert.Conversion",
"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."
"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."
},
{
"name": "stormpath.zuul.account.header.jwt.enabled",
"type": "java.lang.Boolean",
"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.",
"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.",
"defaultValue": true
},
{
Expand Down Expand Up @@ -86,8 +86,8 @@
{
"name": "stormpath.zuul.account.header.jwt.valueClaim.name",
"type": "java.lang.String",
"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.",
"defaultValue": "account"
"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.",
"defaultValue": "user"
},
{
"name": "stormpath.zuul.account.header.jwt.key.alg",
Expand All @@ -114,7 +114,7 @@
{
"name": "stormpath.zuul.account.header.jwt.key.kid",
"type": "java.lang.Boolean",
"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."
"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."
}
],
"hints": [
Expand Down
Loading