diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..ff71a0269 --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +This is a temporary holding area for developer docs and should be removed when we find a proper home for this documentation. diff --git a/docs/chefs-identity-provider-changes.md b/docs/chefs-identity-provider-changes.md new file mode 100644 index 000000000..657a4491f --- /dev/null +++ b/docs/chefs-identity-provider-changes.md @@ -0,0 +1,134 @@ +# CHEFS Identity Provider + +Within the CHEFs application a user's identity provider determines a lot of their access within CHEFs. Keep in mind, this discussion is not on an individual form, this is what menu items, what navigation they have at the application level. + +A User's Identity Provider (IDP) is who vouches for them. In a simplified manner: they provide a username and password (generally) and an Identity Provder verifies them and they end up with a token. Currently for CHEFs we have 3 Identity Providers: `IDIR`, `BCeID Basic` and `BCeID Business`. `IDIR` is for employees/contractors on the BC Government. In CHEFs, the `IDIR` Identity Provider allows for greater power within CHEFs; as far as the CHEFs application is concerned IDIR is the `primary` Identity Provider. + +Previously, all IDP logic was hardcoded within the frontend code and was difficult to change and maintain. + +**Example pseudocode:** + +``` + if user has idp === 'IDIR' then + enable create forms button +``` + +By removing the hardcode, we can add in new IDPs and redefine which IDP is the `primary`. This opens up CHEFs for installations in non-BC Government environments. + +## Identity Provider Table +Columns are added to the Identity Provider table to support runtime configuration. + +* `primary`: boolean, which IDP is the highest level access (currently IDIR) +* `login`: boolean, if this IDP should appear as a login option (Public does not) +* `permissions`: string array, what permissions within CHEFS (not forms) does this IDP have +* `roles`: string array, what Form Roles does this IDP have (designer, owner, submitter, etc) +* `tokenmap`: json blob. this contains the mapping of IDP token fields to userInfo fields. +* `extra`: json blob. this is where non-standard configuration goes. we don't want a column for everything. + +### Application Permissions + +We have removed this hardcoded dependency and create a set of Application Permissions to replace `if user has idp` logic. We can now use `if user has application permission`. Application Permissions are assigned to one or more IDPs. + +``` + VIEWS_FORM_STEPPER: 'views_form_stepper', + VIEWS_ADMIN: 'views_admin', + VIEWS_FILE_DOWNLOAD: 'views_file_download', + VIEWS_FORM_EMAILS: 'views_form_emails', + VIEWS_FORM_EXPORT: 'views_form_export', + VIEWS_FORM_MANAGE: 'views_form_manage', + VIEWS_FORM_PREVIEW: 'views_form_preview', + VIEWS_FORM_SUBMISSIONS: 'views_form_submissions', + VIEWS_FORM_TEAMS: 'views_form_teamS', + VIEWS_FORM_VIEW: 'views_form_view', + VIEWS_USER_SUBMISSIONS: 'views_user_submissions', +``` + +The application permissions will enable/restrict different sections of the CHEFs application. + +### Form Roles + +Identity Provider also sets the scope of what roles a user can be assigned to an individual form. This was hardcoded and is now part of the Identity Provider configuration. These roles can be assigned to one or more IDPs. + +``` + OWNER: 'owner', + TEAM_MANAGER: 'team_manager', + FORM_DESIGNER: 'form_designer', + SUBMISSION_REVIEWER: 'submission_reviewer', + FORM_SUBMITTER: 'form_submitter', +``` + +### Extra +This is a `json` field with no predetermined structure. For BC Gov, we use it for extra functionality for the BCeID IDPs. + +There are UX "enhancements" (frontend) and user search restrictions (server side) that were hardcoded, so now moved into this `json`. Any use of `extra` should assume that data fields may not exist or have null values. + +Currently, `IDIR` has no data in `extra`. + +``` +{ + formAccessSettings: 'idim', + addTeamMemberSearch: { + text: { + minLength: 6, + message: 'trans.manageSubmissionUsers.searchInputLength', + }, + email: { + exact: true, + message: 'trans.manageSubmissionUsers.exactBCEIDSearch', + }, + }, + userSearch: { + filters: [ + { name: 'filterIdpUserId', param: 'idpUserId', required: 0 }, + { name: 'filterIdpCode', param: 'idpCode', required: 0 }, + { name: 'filterUsername', param: 'username', required: 2, exact: true }, + { name: 'filterFullName', param: 'fullName', required: 0 }, + { name: 'filterFirstName', param: 'firstName', required: 0 }, + { name: 'filterLastName', param: 'lastName', required: 0 }, + { name: 'filterEmail', param: 'email', required: 2, exact: true }, + { name: 'filterSearch', param: 'search', required: 0 }, + ], + detail: 'Could not retrieve BCeID users. Invalid options provided.' + } +} +``` + +### Tokenmap +As part of the transistion to a new managed Keycloak realm, we lose the ability to do mapping of Identity Provider attributes to tokens. We do expect our User Information to be standardized and independent of the IDP, so we need to to the mapping ourselves. + +The `tokenmap` is a `json` blob that is effectively a `userInfo` property name mapped to a `token` attribute. Each Identity Provider must provide a mapping so we can build out our `userInfo` object (our current user). + +``` +// userInfo.property: token attribute +{ + idpUserId: 'bceid_user_guid', + keycloakId: 'bceid_user_guid', + username: 'bceid_username', + firstName: null, + lastName: null, + fullName: 'name', + email: 'email', + idp: 'identity_provider', +} +``` + +Note that the `keycloakId` is a GUID and the standard realm does not provide the data as a true GUID, so we need to format it as we build out our `userInfo` object. + +### code and idp + +Each Identity Provider has a `code` and an `idp`. The `code` never changes and is the `id` and used for referential integrity. Previously, `code` and `idp` were exactly the same. Now that we no longer control the keycloak realm, the actual `idp` values have changed (for `bceid`). + +The `idp` fields represents the name if the Identity Provider as found in Keycloak and as returned in the tokens. Within the frontend code, this value is used for idp `hint` - let Keycloak know which IDP the user wished to use for sign in. + +The code (both server and frontend) is confusing since `code` and `idp` fields were used interchangeably as the values always matched. `IDIR` still does. In the userInfo/currentUser object `idp` property is actually `code`. Sigh. Added an `idpHint` property but this should be changed to frontend and backend are consistent as are the property/fields names. In the frontend Identity Provider `idp` is `hint` or `idpHint`. + +Basically, be aware and cautious with `code`, `idp`, `hint` and `idpHint` until this is addressed. + +## Frontend - idpStore +When the application is loaded, we query and store the Identity Providers. This can be found in `frontend/store/identityProviders.js`. + +This has helper methods for building the login buttons, getting login hints, the primary IDP and getting data from `extra`. All access to the cached IDP data should come through this store. + +## Backend - IdpService +Logic for new Identity Provider fields encapsulated in `components/idpService.js`. The queries and logic for parsing the token (use `tokenmap` field to transform token to userInfo). Also, `userSearch` is here as BCeID has specific requirements that are contained in the `extra` field. + diff --git a/docs/chefs-sso-changes.md b/docs/chefs-sso-changes.md new file mode 100644 index 000000000..0fa71ac34 --- /dev/null +++ b/docs/chefs-sso-changes.md @@ -0,0 +1,232 @@ +# CHEFS Single Sign-On (Keycloak Standard Realm) + +## History +Current state of OIDC sign in is using a custom Keycloak realm, managed by the CHEFs team. This realm uses Identity Providers for: IDIR, BCeID Basic and BCeID Business. + +The custom Keycloak realm allows the CHEFs team complete control over the shape of tokens using Client Scopes and custom mappers. + +Both the server/backend and the frontend have their own service clients: `chefs` and `chefs-frontend` respectively. User sign in through the UX/frontend using the `chefs-frontend` client. This client uses the a `chefs` scope to include security (roles) from the `chefs` client. Basically, the `chefs` client is responsible for security and the `chefs-frontend` allows getting a token through the browser. + +The server based client (`chefs`) requires a `clientId` and `clientSecret` to connect and perform its security duties. Obviously a frontend client cannot be configured with a secret so that's where the two clients came in. + + +``` + "frontend": { +... + "keycloak": { + "clientId": "chefs-frontend", + "realm": "chefs", + "serverUrl": "https://dev.loginproxy.gov.bc.ca/auth" + } + }, + "server": { +... + "keycloak": { + "clientId": "chefs", + "clientSecret": "...", + "publicKey": "...", + "realm": "chefs", + "serverUrl": "https://dev.loginproxy.gov.bc.ca/auth" + }, +... + }, +``` + +When a user would sign in, they would get a token like: + +``` +{ + "exp": 1709164869, + "iat": 1709164569, + "auth_time": 1709164569, + "jti": "4c2fbf8c-518c-484e-8b99-6fc36c9ba12f", + "iss": "https://dev.loginproxy.gov.bc.ca/auth/realms/chefs", + "aud": "chefs", + "sub": "5c3e4a62-974b-4c81-ade5-3f2587d5363c", + "typ": "Bearer", + "azp": "chefs-frontend", + "nonce": "ba7da2cb-fcdf-4146-88b3-cae8e775a891", + "session_state": "6209cc93-9f99-466b-8d15-72f7f6bbc266", + "resource_access": { + "chefs": { + "roles": [ + "user" + ] + } + }, + "scope": "openid chefs", + "sid": "6209cc93-9f99-466b-8d15-72f7f6bbc266", + "identity_provider": "bceid-basic", + "idp_username": "jason.sherman", + "name": "Jason Sherman", + "idp_userid": "22D34CC4510D4943A53362BDECD676C6", + "preferred_username": "22d34cc4510d4943a53362bdecd676c6@bceidbasic", + "given_name": "Jason Sherman", + "email": "jason.sherman@gmail.com" +} +``` + +Note: the `aud`/`audience` is `chefs` even though the client is `chefs-frontend`. And that the `scope` includes `chefs` and also the `resource_access` is qualified by `chefs`. + +The ability for CHEFs to manage our own Keycloak realm allows us to add the scope `chefs` to our `chefs-frontend` client and get data from the `chefs` client included in that token. This also allows the `chefs` client to verify and validate this token. + +### User role + +The user role is added to each user that signs in to the realm. No matter which Identity Provider is used, Keycloak will add a `chefs` user role to that user. This ends up in `resources_access:chefs:roles`. + +## Standard realm limitations + +Moving to the BC Government standard realm will allow CHEFs to use Single Sign-on but will take control over the shape of the token and they types of service clients we can create. This removes our ability to add custom token mappers for each Identity Provider, use custom scopes and removes auto-assignment of roles. + + +## Standard realm changes + +Most significantly, we only use a single client: `chefs-frontend`. The type of client is changed to `Public` and is for browser logins only. This requires no client secret data to be stored or passed through to the frontend. + +There is no need for a backend/server client, but we need to verify the token on each request. And this can be done by asking the OIDC server to verify using JSON Web Key Set (JWKS). So we need configuration to set up the verification. + +### SSO Integration Requests + +To make requests, and to manage the clients: [Common Hosted Single Sign-On (CSS) Console](https://bcgov.github.io/sso-requests) + + +**Example SSO Integration Request** + +``` +Associated Team: + Coco Team +Client Protocol: + OpenID Connect +Client Type: + Public +Usecase: + Browser Login +Project Name: + chefs-frontend +Primary End Users: + People living in BC, People doing business/travel in BC, BC Gov Employees, Other: public - unauthenticated +Identity Providers Required: + IDIR, Basic BCeID, Business BCeID +Dev Redirect URIs: + https://chefs-dev.apps.silver.devops.gov.bc.ca/* + https://chefs-fider.apps.silver.devops.gov.bc.ca/* + https://dev.loginproxy.gov.bc.ca/* +Test Redirect URIs: + https://chefs-fider.apps.silver.devops.gov.bc.ca/* + https://chefs-test.apps.silver.devops.gov.bc.ca/* + https://test.loginproxy.gov.bc.ca/* +Prod Redirect URIs: + https://chefs-fider.apps.silver.devops.gov.bc.ca/* + https://submit.digital.gov.bc.ca/app +``` + +** IMPORTANT** the client id will not be `chefs-frontend`, but will have some numerical suffix for each environment is deployed. Ex. `chefs-frontend-5299` for development. + +#### Admin role +This console will allow us to create `admin` role and then assign that role to users who have signed in using our client. Fairly similar process to what we have now (except we cannot assign by adding a user to a group). + +### Identity Providers +Although we have the same identity providers: `IDIR`, `BCeID Basic` and `BCeID Business`, they are named differently. This means the values in tokens for `identity_provider` attribute and used as `idpHints` are different. + +In our custom realm: `idir`, `bceid-basic` and `bceid-business`. + +In standard realm: `idir`, `bceidbasic` and `bceidbusiness`. + +We address this in our IdentityProvider table via `code` and `idp` where `idp` is the Keycloak Identity provider name. + + +### Token Changes + +Since we lose the ability to add custom mappers and the tokens are different for each Identity Provider. + +For instance, in each IDP we would map an attribute (`idir_username`, `bceid_username`) that would end up in the token as `idp_username`. So the token would be consistent. So, in the frontend and token parsing is inconsistent as we lose our `idp_XXX` fields. We handle this in the server as we build our user objects by reading a configuration that maps token attributes to user attribues. + +**NOTE** maybe we should place similar logic in the frontend. We do have the IDP configuration cached so we can use that to write a parsing function. + +Summary: +1. `identity_provider` attribute values have changed +2. `resource_access` no longer supplied, replace with a similar list of roles: `client_roles` +3. `idp_XXX` attributes no longer exist, each IDP has a unique set of attributes. There is overlap on some attributes. + + +### CHEFs Configuration + +Configuration for the frontend does not change signifcantly (nor does the actual javascript/Vue code to interact with the library). We do need to add in a `logoutUrl`. + +However the server configuration changes significantly; as does the code base. + +**Example configuration** + +``` + "frontend": { +... + "oidc": { + "clientId": "chefs-frontend-localhost-5300", + "realm": "standard", + "serverUrl": "https://dev.loginproxy.gov.bc.ca/auth", + "logoutUrl": "https://logon7.gov.bc.ca/clp-cgi/logoff.cgi?retnow=1&returl=https%3A%2F%2Fdev.loginproxy.gov.bc.ca%2Fauth%2Frealms%2Fstandard%2Fprotocol%2Fopenid-connect%2Flogout" + } + }, + "server": { +... + "oidc": { + "realm": "standard", + "serverUrl": "https://dev.loginproxy.gov.bc.ca/auth", + "jwksUri": "https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs", + "issuer": "https://dev.loginproxy.gov.bc.ca/auth/realms/standard", + "audience": "chefs-frontend-localhost-5300", + "maxTokenAge": "300" + }, +... + }, +``` + +Note that the configuration block key has changed from `keycloak` to `oidc`. This is mainly to allow two completely different CHEFs instances running side by side in our development namespace. As all instances share the same config maps/secrets, we need to deploy a new config map for this transition. + +The server configuration now uses the frontend `clientId` as the `audience`. We expect the token to come from a particular issuer for a particular client. + +**IMPORTANT** unclear if verifying the `audience/clientId` will allow true single sign-on. Will have to consult with the SSO team and maybe loosen our verify call to only check token age and issuer. + +#### Logout URL + +The addition of the logout url is to support logging out from Siteminder and Keycloak. Note that the configuration contains only part of the complete logout url as we need to build the redirect url at runtime and add in a `client_id`. + +See note [here](https://github.com/bcgov/keycloak-example-apps/blob/4fdf10494dea8b14d460c2d4a8648f0fdccb965c/examples/oidc/public/vue/src/services/keycloak.js#L36). + + +### OIDC Config Map +Add a new OIDC Config map (no differentition for frontend/server as it is the same client). + +```sh +oc create -n $NAMESPACE configmap $APP_NAME-oidc-config \ + --from-literal=OIDC_REALM=standard \ + --from-literal=OIDC_SERVERURL=https://dev.loginproxy.gov.bc.ca/auth \ + --from-literal=OIDC_JWKSURI=https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs \ + --from-literal=OIDC_ISSUER=https://dev.loginproxy.gov.bc.ca/auth/realms/standard \ + --from-literal=OIDC_CLIENTID=chefs-frontend-5299 \ + --from-literal=OIDC_MAXTOKENAGE=300 \ + --from-literal=OIDC_LOGOUTURL='https://logon7.gov.bc.ca/clp-cgi/logoff.cgi?retnow=1&returl=https%3A%2F%2Fdev.loginproxy.gov.bc.ca%2Fauth%2Frealms%2Fstandard%2Fprotocol%2Fopenid-connect%2Flogout' +``` + +### Backend code changes + +Significant changes to server/backend code. Most notably we remove `keycloak-connect` library. Keycloak keeps threatening to deprecate this library, so good to get rid of it. However, it did provide a lot of useful middleware that we've had to replicate. + +Most logic is found in `components/jwtService.js` including the `protect` middleware. Changes to the token and how we map to a user are found in `components/idpService.js`. + + +### Frontend code changes + +Basically the frontend remains the same as we continue to use the same library: `keycloak-js`. + +The `init` is slightly different as we move to a `public` client, we need to specify that we want to use `pkceMethod`: + +``` + init: { pkceMethod: 'S256', checkLoginIframe: false, onLoad: 'check-sso' }, +``` + +Changes to the token mean we change how we determine roles. We no longer qualify by resource (`chefs`). and we get the data from `client_roles`. + +Since we added the `logoutUrl`, the logout method has changed too. `logoutUrl` is optional, which will make it easier for non-BC installations. See the auth store (`store/auth.js`). + + diff --git a/docs/chefs-token-and-userinfo-changes.md b/docs/chefs-token-and-userinfo-changes.md new file mode 100644 index 000000000..a17d66e01 --- /dev/null +++ b/docs/chefs-token-and-userinfo-changes.md @@ -0,0 +1,409 @@ +# CHEFs User and Standard Realm Tokens + + + + + + + + + + + + +
Custom Realm Standard Realm (SSO)
+{
+  "exp": 1709324197,
+  "iat": 1709323897,
+  "auth_time": 1709323896,
+  "jti": "32353e01-3ebf-402f-9ef0-1d56c595aa55",
+  "iss": "https://dev.loginproxy.gov.bc.ca/auth/realms/chefs",
+  "aud": "chefs",
+  "sub": "bdd91117-55ed-47fd-ae23-365a25fae566",
+  "typ": "Bearer",
+  "azp": "chefs-frontend",
+  "nonce": "2bfa957e-b8bf-4072-8720-94adee440c4d",
+  "session_state": "8799a22f-5f93-4c04-813b-c637b1b81687",
+  "resource_access": {
+    "chefs": {
+      "roles": [
+        "admin",
+        "user"
+      ]
+    }
+  },
+  "scope": "openid chefs",
+  "sid": "8799a22f-5f93-4c04-813b-c637b1b81687",
+  "identity_provider": "idir",
+  "idp_username": "JPERRY",
+  "name": "Joe Perry",
+  "idp_userid": "584861AA34E546F8BDA6A7004DC9C6C9",
+  "preferred_username": "584861aa34e546f8bda6a7004dc9c6c9@idir",
+  "given_name": "Joe",
+  "family_name": "Perry",
+  "email": "joe.perry@gov.bc.ca"
+}
+
+{
+  "exp": 1709322907,
+  "iat": 1709322607,
+  "auth_time": 1709322607,
+  "jti": "5f4088e8-8e55-49fa-8df5-9ebfa6f585b5",
+  "iss": "https://dev.loginproxy.gov.bc.ca/auth/realms/standard",
+  "aud": "chefs-frontend-5299",
+  "sub": "584861aa34e546f8bda6a7004dc9c6c9@idir",
+  "typ": "Bearer",
+  "azp": "chefs-frontend-5299",
+  "nonce": "33974cb4-7607-4f1f-80e3-c129e40436cf",
+  "session_state": "1893ab3b-410f-48c8-8274-cad94f4812ab",
+  "scope": "openid idir bceidbusiness email profile bceidbasic",
+  "sid": "1893ab3b-410f-48c8-8274-cad94f4812ab",
+  "idir_user_guid": "584861AA34E546F8BDA6A7004DC9C6C9",
+  "client_roles": [
+    "admin"
+  ],
+  "identity_provider": "idir",
+  "idir_username": "JPERRY",
+  "email_verified": false,
+  "name": "Perry, Joe CITZ:EX",
+  "preferred_username": "584861aa34e546f8bda6a7004dc9c6c9@idir",
+  "display_name": "Perry, Joe CITZ:EX",
+  "given_name": "Joe",
+  "family_name": "Perry",
+  "email": "joe.perry@gov.bc.ca"
+}
+
+{
+  "id": "c6042253-da3f-49d3-bb7d-595ec68fd780",
+  "usernameIdp": "JPERRY@idir",
+  "idpUserId": "584861AA34E546F8BDA6A7004DC9C6C9",
+  "keycloakId": "bdd91117-55ed-47fd-ae23-365a25fae566",
+  "username": "JPERRY",
+  "firstName": "Joe",
+  "lastName": "Perry",
+  "fullName": "Joe Perry",
+  "email": "joe.perry@gov.bc.ca",
+  "idp": "idir",
+  "public": false,
+  "forms": []
+}
+
+{
+  "id": "a0c195aa-57d9-4a70-8169-588876917765",
+  "usernameIdp": "JPERRY@idir",
+  "idpUserId": "584861AA34E546F8BDA6A7004DC9C6C9",
+  "keycloakId": "584861AA-34E5-46F8-BDA6-A7004DC9C6C9",
+  "username": "JPERRY",
+  "firstName": "Joe",
+  "lastName": "Perry",
+  "fullName": "Perry, Joe CITZ:EX",
+  "email": "joe.perry@gov.bc.ca",
+  "idp": "idir",
+  "public": false,
+  "idpHint": "idir",
+  "forms": []
+}
+ +# BCeID Basic + + + + + + + + + + + + +
Custom Realm Standard Realm (SSO)
+{
+  "exp": 1709324355,
+  "iat": 1709324055,
+  "auth_time": 1709324042,
+  "jti": "ac68f321-4e42-4b5c-907f-34c0485410af",
+  "iss": "https://dev.loginproxy.gov.bc.ca/auth/realms/chefs",
+  "aud": "chefs",
+  "sub": "5b3d4a62-974b-4c81-adf5-3e2587d5363c",
+  "typ": "Bearer",
+  "azp": "chefs-frontend",
+  "nonce": "86984a12-77de-4910-8ae5-88d3e204038c",
+  "session_state": "28cafa4e-0ef8-4ab3-8a14-9d125fbbb8ad",
+  "resource_access": {
+    "chefs": {
+      "roles": [
+        "user"
+      ]
+    }
+  },
+  "scope": "openid chefs",
+  "sid": "28cafa4e-0ef8-4ab3-8a14-9d125fbbb8ad",
+  "identity_provider": "bceid-basic",
+  "idp_username": "joe.perry",
+  "name": "Joe Perry",
+  "idp_userid": "11D34CC4510D4943A53362BDECD676C6",
+  "preferred_username": "11d34cc4510d4943a53362bdecd676c6@bceidbasic",
+  "given_name": "Joe Perry",
+  "email": "joe.perry@gmail.com"
+}
+{
+  "exp": 1709323834,
+  "iat": 1709323534,
+  "auth_time": 1709323533,
+  "jti": "889f9919-fcc3-4f4b-b6ac-7d2be0a51ca0",
+  "iss": "https://dev.loginproxy.gov.bc.ca/auth/realms/standard",
+  "aud": "chefs-frontend-5299",
+  "sub": "11d34cc4510d4943a53362bdecd676c6@bceidbasic",
+  "typ": "Bearer",
+  "azp": "chefs-frontend-5299",
+  "nonce": "cba89d3f-dd38-44f6-81cb-d412df5c0570",
+  "session_state": "00fad75b-25c5-42d7-af05-8e992e283a01",
+  "scope": "openid idir bceidbusiness email profile bceidbasic",
+  "sid": "00fad75b-25c5-42d7-af05-8e992e283a01",
+  "client_roles": [
+    "admin"
+  ],
+  "bceid_user_guid": "11D34CC4510D4943A53362BDECD676C6",
+  "identity_provider": "bceidbasic",
+  "bceid_username": "joe.perry",
+  "email_verified": false,
+  "name": "Joe Perry",
+  "preferred_username": "11d34cc4510d4943a53362bdecd676c6@bceidbasic",
+  "display_name": "Joe Perry",
+  "given_name": "Joe Perry",
+  "family_name": "",
+  "email": "joe.perry@gmail.com"
+}
+{
+  "id": "cdaeea76-eadb-4eb5-b8e6-bde57f1d65c8",
+  "usernameIdp": "joe.perry@bceid-basic",
+  "idpUserId": "11D34CC4510D4943A53362BDECD676C6",
+  "keycloakId": "5b3d4a62-974b-4c81-adf5-3e2587d5363c",
+  "username": "joe.perry",
+  "firstName": "Joe Perry",
+  "fullName": "Joe Perry",
+  "email": "joe.perry@gmail.com",
+  "idp": "bceid-basic",
+  "public": false,
+  "forms": []
+}
+{
+  "id": "6a6a8134-5dcb-4e77-8ac8-44d59391690c",
+  "usernameIdp": "joe.perry@bceid-basic",
+  "idpUserId": "11D34CC4510D4943A53362BDECD676C6",
+  "keycloakId": "11D34CC4-510D-4943-A533-62BDECD676C6",
+  "username": "joe.perry",
+  "fullName": "Joe Perry",
+  "email": "joe.perry@gmail.com",
+  "idp": "bceid-basic",
+  "public": false,
+  "idpHint": "bceidbasic",
+  "forms": []
+}
+ + +# BCeID Business + + + + + + + + + + + + +
Custom Realm Standard Realm (SSO)
+{
+  "exp": 1709324544,
+  "iat": 1709324244,
+  "auth_time": 1709324232,
+  "jti": "60918b9c-82b5-4fa6-aec7-64aa54ec031a",
+  "iss": "https://dev.loginproxy.gov.bc.ca/auth/realms/chefs",
+  "aud": "chefs",
+  "sub": "429b39bc-fa98-4169-a25e-0139f0ae689d",
+  "typ": "Bearer",
+  "azp": "chefs-frontend",
+  "nonce": "e43e0a35-5d4e-4a56-82d7-b258444f4ac6",
+  "session_state": "7bb75437-8fc7-44a5-b96b-5f885d9e534f",
+  "resource_access": {
+    "chefs": {
+      "roles": [
+        "user"
+      ]
+    }
+  },
+  "scope": "openid chefs",
+  "sid": "7bb75437-8fc7-44a5-b96b-5f885d9e534f",
+  "identity_provider": "bceid-business",
+  "idp_username": "stevieray",
+  "name": "Stevie Ray-Vaughan",
+  "idp_userid": "F8F0E333E79C4AD183D19C9377498785",
+  "preferred_username": "f8f0e333e79c4ad183d19c9377498785@bceidbusiness",
+  "given_name": "Stevie Ray-Vaughan",
+  "email": "stevie.ray@gov.bc.ca"
+}
+{
+  "exp": 1709323929,
+  "iat": 1709323629,
+  "auth_time": 1709323628,
+  "jti": "64064578-67b4-4248-a267-125a5a87848e",
+  "iss": "https://dev.loginproxy.gov.bc.ca/auth/realms/standard",
+  "aud": "chefs-frontend-5299",
+  "sub": "f8f0e333e79c4ad183d19c9377498785@bceidbusiness",
+  "typ": "Bearer",
+  "azp": "chefs-frontend-5299",
+  "nonce": "54e10ed1-c0a7-4fce-9c4a-21d8e85ac076",
+  "session_state": "f684d70a-c894-47d1-af38-f746f038b176",
+  "scope": "openid idir bceidbusiness email profile bceidbasic",
+  "sid": "f684d70a-c894-47d1-af38-f746f038b176",
+  "bceid_business_guid": "B50E1574C1A944189BC661DED01345FB",
+  "bceid_business_name": "texasflood",
+  "bceid_user_guid": "F8F0E333E79C4AD183D19C9377498785",
+  "bceid_username": "stevieray",
+  "email_verified": false,
+  "preferred_username": "f8f0e333e79c4ad183d19c9377498785@bceidbusiness",
+  "display_name": "Stevie Ray-Vaughan",
+  "given_name": "Stevie Ray-Vaughan",
+  "client_roles": [
+    "admin"
+  ],
+  "identity_provider": "bceidbusiness",
+  "name": "Stevie Ray-Vaughan",
+  "family_name": "",
+  "email": "stevie.ray@gov.bc.ca"
+}
+{
+  "id": "ed1dfcbb-d2e0-448e-b4f2-7b5808d7f4a3",
+  "usernameIdp": "stevieray@bceid-business",
+  "idpUserId": "F8F0E333E79C4AD183D19C9377498785",
+  "keycloakId": "429b39bc-fa98-4169-a25e-0139f0ae689d",
+  "username": "stevieray",
+  "firstName": "Stevie Ray-Vaughan",
+  "fullName": "Stevie Ray-Vaughan",
+  "email": "stevie.ray@gov.bc.ca",
+  "idp": "bceid-business",
+  "public": false,
+  "forms": []
+}
+{
+  "id": "8a2e1c04-ace2-414a-a5f0-9627e2f8b3ba",
+  "usernameIdp": "stevieray@bceid-business",
+  "idpUserId": "F8F0E333E79C4AD183D19C9377498785",
+  "keycloakId": "F8F0E333-E79C-4AD1-83D1-9C9377498785",
+  "username": "stevieray",
+  "fullName": "Stevie Ray-Vaughan",
+  "email": "stevie.ray@gov.bc.ca",
+  "idp": "bceid-business",
+  "public": false,
+  "idpHint": "bceidbusiness",
+  "forms": []
+}
+ + +## Token Key Differences + +### idp\_userid / idir\_user\_guid / bceid\_user\_guid + +In the custom realm, we mapped `idp_userid` to the `idpUserId`. + +There is no `idp_userid` attribute in standard realm tokens. But `idp_userid` was mapped from `idir_user_guid` and `bceid_user_guid` (depending on the IDP). + +As we parse tokens we will be setting the `idpUserId` correctly and it since that value comes from the IDP and not Keycloak, it matches in both realms. + +### idp\_username + +`idp_username` is a custom mapped field so it doesn't exist in standard realm tokens, it is used to populate the userInfo field: `username`. In the standard realm we pull from `idir_username` or `bceid_username`. + +### display\_name + +Standard realm returns a `display_name` attribute, but it appears to be the same as `name`. + +### IDIR name / display\_name + +The standard realm IDIR provider returns a name with Ministry information: + +`Perry, Joe CITZ:EX` + +this maps to userInfo `fullName` which is very different that our custom realm mapping (`Joe Perry`). + +### sub / keycloakId +In custom realm the subject is a GUID. We use this as a `keycloakId`. + +`"sub": "bdd91117-55ed-47fd-ae23-365a25fae566",` + +In the standard realm the subject matches the `preferred_username` and is not a GUID. However, `idir_user_guid` and `bceid_user_guid` are *almost* GUIDs and can be transformed easily. So we can use this as the `keycloakId`. + +`keycloakId` is no longer a useful field, it was only used to jump into the custom realm Keycloak Admin console which is no longer allowed for us in the standard realm. + + +**MIGRATION NOTE** update `keycloakId` to match `idpUserId` as `idpUserId` will match between realms. + +### identity\_provider + +In custom realm, `BCeID Basic` = `bceid-basic` and `BCeID Business` = `bceid-business`. + +In standard realm they are `bceidbasic` and `bceidbusiness` respectively. Hints passed to Keycloak match the `identity_provider` and need updating. + + +### resource\_access / client\_roles + +``` + "resource_access": { + "chefs": { + "roles": [ + "admin", + "user" + ] + } + }, + + ... + + "client_roles": [ + "admin" + ], +``` + +`resource_access` no longer exists, but we have `client_roles`. There is no `user` role, and roles are not qualified by a specific resource (ie. `chefs`) they are just a list of role names. + + +## User table and UserInfo/CurrentUser + +In our custom realm, no matter what Identity Provider was used, the token contained the same attributes. Mapping a token to a userInfo object (ie. currentUser) is straightforward. + +In the standard realm, we need dynamic mapping. To achieve this, we now store a map in our IdentityProvider table: `tokenmap`. This is how we determine which token attribute value becomes the userInfo attribute value. + +One key point is the `keycloakId` field requires a GUID, in the mapping (mapped to `idpUserId`) we take a field that is GUID-like and format it to be a GUID. + +### idpUserId +The `idpUserId` field is our non-key unique field and is used to actually identify the user from the token. Since it comes from the Identity Provider it is consistent across realms. + +### userInfo idp and idpHint + +In the custom realm, as we parse out the token to make the userInfo object we add in a field: `idpHint` that contains the actual token `identity_provider`. The now poorly named `idp` attribute contains the `code` from the IdentityProvider table. + +A work item should be created to make the userInfo object consistent with the token and frontend code where we have IDPs as `code`, `display` and `hint`. It may be more trouble than it is worth to rename the column and the views, but the transformation codes (token -> userInfo) should return an object consistent with naming conventions used in frontend logic so we always know if we are using our CHEFs IdentityProvider table `code` value or `hint` value. + +## Data Migration + +Since `keycloakId` is no longer a useful field and that is the only realm specific data, we are good for data migration. `keycloakId` will be updated as `idpUserId` (`idir_user_guid` or `bceid_user_guid` in GUID format). Any other data (`name`) will also be updated during the normal course of login. + +**API call flow ** + +* get current user + * get bearer token + * validate token +* set request user + * get token payload + * login + * parse token (use the IDP `tokenmap`) + * get user id (find by `idpUserId` + * create user if not found + * update user fields if found + + +`idpUserId` remains the same across realms since it comes from the Identity Provider, all user fields will be updated if the user exists, otherwise we create a new one. \ No newline at end of file