Skip to content

Add BC Services Card sign-in to appointment booking#1093

Open
veenupunyani wants to merge 2 commits into
developfrom
Sign-in-with-BC-Services-Card
Open

Add BC Services Card sign-in to appointment booking#1093
veenupunyani wants to merge 2 commits into
developfrom
Sign-in-with-BC-Services-Card

Conversation

@veenupunyani

@veenupunyani veenupunyani commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary###

  1. Added BC Services Card (Keycloak) sign-in to appointment-booking as booking step 3.
  2. Wired the flow: /services → /service-locations → /login → /signin/bcsc → /login (signed in).
  3. Restricted sign-in to BCSC only and showed /login?error=idp for other IdPs. This was hint give by Marco in teams SSO channel.
  4. Persisted selected service/location in sessionStorage across the IdP redirect; logout cleared auth and booking.
  5. Updated the user on the server after login, then continued without waiting for that request to finish.
  6. Showed signed-in name + Log out in the header.
  7. Updated local Keycloak for localhost:5173 and BCSC-tagged test users.

Test plan locally

  1. Select service → location → Continue → /login shows BCSC button
  2. Login with local BCSC users → return to /login signed in with selections kept
  3. Header Log out → /services, auth + booking storage cleared
  4. Refresh on /login while signed in: Selections persisted
  5. Hit logout to test logout.

Test on OpenShift

1 - Visit - https://dev-appointments.apps.silver.devops.gov.bc.ca/
2 - Sign in using test username and password given in email.

Add BCSC Keycloak login as booking step
it kept looping after singing in
@sonarqubecloud

Copy link
Copy Markdown

@chrsamp chrsamp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid @veenupunyani, left some comments in the review. I've also added https://citz-sbc.atlassian.net/browse/DEV-78 to our next sprint to handle refreshing the user's token.

"@react-router/serve": "8.0.0",
"geolib": "^3.3.14",
"isbot": "^5.1.36",
"keycloak-js": "^24.0.5",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using such an old version of this package?

"VUE_APP_ROOT_API": "http://localhost:5000/api/v1"
"VUE_APP_ROOT_API": "http://localhost:5000/api/v1",
"KEYCLOAK_CONFIG_URL": "/config/kc/keycloak-public.json",
"BCServicesCardUrl": "https://www2.gov.bc.ca/gov/content?id=B2B3A21E797A421A8FD39EEA86E245D6"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style note: use SCREAMING_SNAKE_CASE like the other env vars already are.

@@ -2,43 +2,62 @@
// Each environment has its own ConfigMap with the same filename but different values.

type RuntimeConfig = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment about the config key names in configuration.json. If you want to be more idiomatic you can map the keys like KEYCLOAK_CONFIG_URL to KeycloakConfigUrl and manually create the RuntimeConfig instance instead of just casting the JSON response to that type, but that's more work. Either way, I would pick one style here as well, and stick with it (either PascalCase or SCREAMING_SNAKE_CASE).

Comment on lines 5 to 10
import { getBookingLocations, type Location } from '../api/locations'
import type { Service } from '../api/services'
import { useBooking } from '../booking/booking-context'
import { useBooking } from '~/booking/booking-context'
import { BookingBackRow } from '../components/BookingBackRow'
import { BookingContinueRow } from '../components/BookingContinueRow'
import { BookingStepProgress } from '../components/BookingStepProgress'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First thought when reading this: use the ~ alias consistently (across all your imports) or stick with relative paths, I wouldn't just replace one.

} as const

// Citizen booking accepts only BCSC for now (OTP may be added later).
export const ALLOWED_BOOKING_IDPS = [IdpHint.BCSC] as const

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fun, smart! 👍 We could enable and disable IdP's without removing them from the app.

Comment on lines +77 to +87
function resolveFullName(claims: TokenClaims): string {
const firstName = claims.firstname || claims.given_name || ''
const lastName = claims.lastname || claims.family_name || ''
return (
`${firstName} ${lastName}`.trim() ||
claims.display_name ||
claims.name ||
claims.preferred_username ||
''
)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid creating a user's name using a first name/last name order, this is not consistent across all cultures. Prefer display_name instead or return something like email (if we know we get this from every supported IdP, I believe we do) or some generic string like "Appointment User". Please don't expose the preferred_username claim to the user themselves, it's not private but e.g. for BCSC is the users DID (like a GUID).

https://bcgov.github.io/sso-docs/advanced/identity-mappers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants