|
| 1 | +# Dex Enhancement Proposal (DEP) 2876 - 2023-05-17 - Extend payload |
| 2 | + |
| 3 | +## Table of Contents |
| 4 | + |
| 5 | +- [Summary](#summary) |
| 6 | +- [Motivation](#motivation) |
| 7 | + - [Goals/Pain](#goals) |
| 8 | + - [Non-Goals](#non-goals) |
| 9 | +- [Proposal](#proposal) |
| 10 | + - [User Experience](#user-experience) |
| 11 | + - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) |
| 12 | + - [Risks and Mitigations](#risks-and-mitigations) |
| 13 | + - [Alternatives](#alternatives) |
| 14 | +- [Future Improvements](#future-improvements) |
| 15 | + |
| 16 | +## Summary |
| 17 | + |
| 18 | +Dex has very rigid claims support. Adding additional claims via a connector requires forking the code and making changes to various internal structures. There are a number of proposals where this discussed, namely #1635 . |
| 19 | + The scope of these is fairly large and we are exploring a possible solution that limits itself to just |
| 20 | +mutating of the claims. |
| 21 | + |
| 22 | +## Context |
| 23 | + |
| 24 | +- #1636 |
| 25 | + |
| 26 | +## Motivation |
| 27 | + |
| 28 | +### Goals |
| 29 | + |
| 30 | +The working goals of this proposal are: |
| 31 | + |
| 32 | +- Minimal changes to Dex core source |
| 33 | +- Provide an optional Go Interface specification connectors can implement to support extending token payload |
| 34 | +- Limit the interface to a sing method which allows mutating all claims before Dex core signs and delivers it |
| 35 | + |
| 36 | +### Non-goals |
| 37 | + |
| 38 | +- We will not explore dynamic loading. This is a future topic. |
| 39 | + |
| 40 | +## Proposal |
| 41 | + |
| 42 | +### User Experience |
| 43 | + |
| 44 | +The implementation is fully backwards compatible. Users not requiring this |
| 45 | +feature won't have to change anything in their Dex deployment. |
| 46 | + |
| 47 | +We expect that connectors will expose additional configuration options to allow for customizations |
| 48 | +of the payload extender functionality. Examples are toggles to include/exclude certain IDP claims |
| 49 | + |
| 50 | +### Implementation Details/Notes/Constraints |
| 51 | + |
| 52 | +We propose adding a new PayloadExtender interface which connectors can choose to implement: |
| 53 | + |
| 54 | +```golang |
| 55 | +// PayloadExtender allows connectors to enhance the payload before signing |
| 56 | +type PayloadExtender interface { |
| 57 | + ExtendPayload(scopes []string, payload []byte, connectorData []byte) ([]byte, error) |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +The `ExtendPayload` method will be called just before the `JWT` is signed by Dex core. |
| 62 | + |
| 63 | +By implementing this interface connectors get a chance to mutate the `id_token` payload |
| 64 | +before signature creation. The `scopes` may be used to perform conditional mutation. |
| 65 | +The `payload` is passed as a byte array as well as the `connectorData` associated with the authorization request. This allows the connector to pass session specific context via `connectorData`. |
| 66 | +The resulting mutated structure is returned including an `error` condition. |
| 67 | + |
| 68 | +> A separate DEP will be created to propopse `Dynamic Scopes support` so payload extending can be application driven. |
| 69 | +
|
| 70 | +### Working example |
| 71 | + |
| 72 | +A working example of this interface implementation could look like this: |
| 73 | + |
| 74 | +```golang |
| 75 | +func (c *hsdpConnector) ExtendPayload(scopes []string, payload []byte, cdata []byte) ([]byte, error) { |
| 76 | + var cd connectorData |
| 77 | + var originalClaims map[string]interface{} |
| 78 | + |
| 79 | + c.logger.Info("ExtendPayload called") |
| 80 | + |
| 81 | + if err := json.Unmarshal(cdata, &cd); err != nil { |
| 82 | + return payload, err |
| 83 | + } |
| 84 | + if err := json.Unmarshal(payload, &originalClaims); err != nil { |
| 85 | + return payload, err |
| 86 | + } |
| 87 | + |
| 88 | + // Experimental teams |
| 89 | + var teams []string |
| 90 | + teams = append(teams, cd.Introspect.Organizations.ManagingOrganization) |
| 91 | + originalClaims["teams"] = teams |
| 92 | + |
| 93 | + extendedPayload, err := json.Marshal(originalClaims) |
| 94 | + if err != nil { |
| 95 | + return payload, err |
| 96 | + } |
| 97 | + return extendedPayload, nil |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +This results in a `teams` claim being available in the id_token. |
| 102 | + |
| 103 | +### Risks and Mitigations |
| 104 | + |
| 105 | +Since the claims are mutated / extended from the upstream IDP, any consuming application should |
| 106 | +be aware of active payload extending of the connector. Care should be taken by the extender not to |
| 107 | +break or remove standard claims. All ExtendPayload implementation should include tests to cover |
| 108 | +all common scenarios. |
| 109 | + |
| 110 | +Any connector not implementing the `ExtendPayload` interface should function as before. |
| 111 | + |
| 112 | +### Alternatives |
| 113 | + |
| 114 | +- We looked at KeyCloak to support this but Dex is way lighter in deployment and maintenance. |
| 115 | +- Commercial IDP offerings have similar filtering/mutating capabilities but have a lock-in disadvantage. |
| 116 | + |
| 117 | +## Future Improvements |
| 118 | + |
| 119 | +- An DEP will be submitted to support `Dynamic scopes support` |
| 120 | +- We want to explore implementing a generic pluggable filtering mechanism which is connector agnostic |
0 commit comments