077-merchant-self-provisioning.rst (9778B)
1 DD 77: Merchant Multi-Tenancy and Self-Provisioning 2 ################################################### 3 4 :Design status: Abandoned 5 :Implementation status: Not started 6 :DD shepherd: TBD 7 :Historical contributors: Martin Schanzenbach 8 :First published: 2025-12-11 9 :Last substantive change: 2025-12-11 10 11 .. warning:: 12 13 This proposal was abandoned. The OIDC-based multi-tenancy model below was 14 not implemented and must not be treated as the merchant authentication or 15 provisioning contract. 16 17 Summary 18 ======= 19 20 A new requirement is planned that allows a self-provisioning feature of instances in the merchant backend. 21 22 Motivation 23 ========== 24 25 We want to enable self-provisioning feature of instances in the merchant backend. 26 A lot of if not most banks have an OpenID-Connect-based IdP that has all their customers already enrolled. 27 Banks may choose to run merchant instances for their customers (Merchant-as-a-Service). 28 In order to simplify enrollment and facilitate adoption, it should be possible to authenticate 29 against such an IdP and allow users to create and use their own instances. 30 31 This conflates self-provisioning itself with OIDC integration on purpose: 32 Self-provisioning without OIDC integration provides little benefit to users. 33 Only if they are enabled to re-use their accounts/credentials of the Merchant-as-a-Service provider 34 this feature becomes useful. 35 36 The limitation on OIDC is also on purpose: It is the de-facto standard for Identity Federation. 37 38 39 Requirements 40 ============ 41 42 1. Support OpenID-Connect authentication. 43 2. Support self-provisioning of instances for users through the API/UI. 44 3. Instances can be associated with users through the API/UI. 45 46 Proposed Solution 47 ================= 48 49 The proposed solution is as follows: 50 51 User 52 ---- 53 54 A user consists of the following properties: 55 56 1. User ID 57 2. Associated instances (may be empty) 58 3. Password (optional) 59 4. External IdP 60 61 Instance association can also be viewed as a property of the instance (associated users) but 62 effectively it will be its own table mapping instances to users (n*m). 63 64 OIDC login: 65 ----------- 66 67 (Note that the following assumes that the user logged in before). 68 69 Two new endpoints must be implemented: ``/oidc-login`` and ``/oidc-callback``. 70 The ``/oidc-login`` endpoint is used when the user clicks on *Login with OIDC* button on the login page of the 71 Merchant Backoffice. The endpoint will redirect the user to the IdP, startin the OIDC flow. 72 The flow will be initiated with the ``/oidc-callback`` endpoint as ``redirect_uri``, meaning that the user upon 73 successful authentication will be redirected to ``/oidc-callback`` with an authorization code. 74 The merchant backend will exchange the code for ID/access token, and return a cookie associating this state with the new user session. 75 The SPA can then use the Cookie as a credential at the existing ``/token`` endpoint to receive a native merchant access token, at which point the session cookie expires. 76 77 The OIDC IdP is configured globally (not per instance) by the admin in ``merchant.conf``. 78 79 Self provisioning: 80 ------------------ 81 82 Once a user logs in with an external (OIDC) IdP for the first time, a new user entry is created in the merchant backend which is not associated with any instance. 83 This user/token only has access to the self-service page of the Merchant backoffice UI. 84 The user may create a new instance (and is immediately added as a user to the new instance as its creator). 85 We may want to require that OIDC users have an email address (either as their external ID or as a property) and 86 use this as our local User ID. 87 Alternatively (or additionally), other users may add this new user to their instances. 88 The authorization logic of the merchant backend must be modified such that any user that is not associated with an instance is not allowed to perform any operations on it. 89 For now, all associated users have the same roles/rights and are effectively instance admins. 90 91 Migration: 92 ---------- 93 94 Currently, authentication is tied to the instance itself, which is protected by a password. 95 The current design can be migrated by (automatically) creating a user for each existing instance and its password moved to the new user. 96 The ID of the new user is then also immediately associated with the instance as a valid (admin) user. 97 98 Example: 99 100 :: 101 102 BEGIN; 103 104 -- Check patch versioning is in place. 105 SELECT _v.register_patch('merchant-0028', NULL, NULL); 106 107 SET search_path TO merchant; 108 109 -------------------------- Users --------------------------- 110 111 CREATE TABLE IF NOT EXISTS merchant_users 112 (user_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY 113 ,user_id TEXT NOT NULL UNIQUE 114 ,auth_hash BYTEA CHECK(LENGTH(auth_hash)=64) 115 ,auth_salt BYTEA CHECK(LENGTH(auth_salt)=32) 116 ); 117 COMMENT ON TABLE merchant_users 118 IS 'all the users enrolled in this backend'; 119 COMMENT ON COLUMN merchant_users.user_id 120 IS 'identifier of the user (required)'; 121 COMMENT ON COLUMN merchant_users.auth_hash 122 IS 'hash used for merchant back office authorization, may be NULL (unset)'; 123 COMMENT ON COLUMN merchant_users.auth_salt 124 IS 'salt to use when hashing password before comparing with auth_hash'; 125 126 127 --- FIXME not sure if that is what we want... 128 CREATE TABLE IF NOT EXISTS merchant_instance_users 129 (user_serial BIGINT 130 REFERENCES merchant_users (user_serial) ON DELETE CASCADE, 131 merchant_serial BIGINT 132 REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE ); 133 COMMENT ON COLUMN merchant_instance_users.user_serial 134 IS 'identifies an the admin user of the instance'; 135 136 COMMENT ON COLUMN merchant_login_tokens.merchant_serial 137 IS 'identifies the instance for which the user is admin'; 138 139 140 INSERT INTO merchant_users (user_id, auth_hash, auth_salt) 141 SELECT merchant_id, auth_hash, auth_salt FROM merchant_instances; 142 143 ALTER TABLE merchant_instances 144 DROP COLUMN auth_hash; 145 146 ALTER TABLE merchant_instances 147 DROP COLUMN auth_salt; 148 149 COMMIT; 150 151 Test Plan 152 ========= 153 154 (If this DD concerns a new or changed feature, describe how it can be tested.) 155 156 Locally, OIDC logins can be tested by running a local test OIDC server, e.g. "pipx run oidc-provider-mock" and 157 configuring the endpoints accordingly. 158 159 Definition of Done 160 ================== 161 162 (Only applicable to design documents that describe a new feature. While the 163 DoD is not satisfied yet, a user-facing feature **must** be behind a feature 164 flag or dev-mode flag.) 165 166 Alternatives 167 ============ 168 169 1. No user concept 170 171 It is theoretically possible to implement authentication and self-provisioning without adding the concept of a *User* to the merchant. 172 This will require some gymnastics around the SPA such that first, OIDC users can log-in to an instance at all. 173 This means adding a list of authorized OIDC IDs (careful: Must be unique/include the IdP ID) to the instance. 174 This modification is also required in the proposed solution. 175 This will allow OIDC federated users to log in to *existing* instances for which this login was pre-configured by an admin. 176 In order to further support self-provisioning, we need a mechanism that allows OIDC to log in without an associated instance. 177 This is tricky because the authentication is tightly integrated with the concept of having an instance behind it. 178 The simplest solution would be to have the SPA do the OIDC flow browser side, and allow the self-provisioning API endpoint to accept the OIDC access token (or ID token) as credentials which will create an instance with this identity as authroized admin user. 179 Note that this goes off-spec of OIDC as both the ID token and access token are not supposed to be presented to the Merchant backend API (ID token audience limited to the client = SPA, access token audience is limited to the IdP Userinfo endpoint). 180 181 182 Drawbacks 183 ========= 184 185 Introducing the concept of a User is a rather big change to the authentication logic of the merchant. 186 However, it will edge its architecture conceptually closer to common OIDC-based approaches. 187 Meaning that if in the future the Merchant authentication is delegated completely to an OIDC IdP, this change becomes easier. 188 189 The alternatives do not provide this but also incur rather big changes that are kind of messy as elaborated above. 190 191 Discussion / Q&A 192 ================ 193 194 1. On the difficulty of not having a *User* 195 196 So after some drafting of an OIDC implementation I can see that IF you need some kind of self-service instance creation (via OIDC) I think we really really should add the concept of a user. 197 As long as we only configure an IDP and an authorized user for an instance/instances, the implementation is trivial. Once we need the login before the instance even exists, it gets pretty ugly. 198 I am not sure if I should continue with what I am doing rn until we know what exactly this self provisioning requirement is. Because if we currently authenticate against the instance, and then want to authenticate before the instance even exists, the whole concept falls apart. 199 200 Without the self-service instance creation and only loggin into an existing instance w/o password and with OIDC instead, I can have an implementation done pretty soon. 201 But with self-service instance creation most of that implementation will become obsolete because conceptually, authentication will have to be tied to the user and not the instance. 202 203 What I mean with it gets ugly: What we could do is go completely off spec and have the SPA do the OIDC flow,aquiring the ID and access tokens. Then, we allow some kind of token exchange (OIDC token -> merchant token, kind of ugly as this is not really allowed) with the permission to create instances. Somehow we then have to limit the number of instances that can be created with that token (which is where it gets really ugly)