Skip to content

Commit

Permalink
BCSC Authentication
Browse files Browse the repository at this point in the history
* WIP debug BCSC openID connect flow

* BCSC authentication

* Await all oidcStore calls
  • Loading branch information
pcatkins authored Dec 11, 2023
1 parent a07a321 commit 517d4fa
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
}
},
"cors": {
"allowedOrigins": "https://dev.loginproxy.gov.bc.ca/"
"allowedOrigins": "https://dev.loginproxy.gov.bc.ca/;https://idtest.gov.bc.ca/"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,48 @@
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { defineComponent, watch } from "vue";
import { useRouter } from "vue-router";
import { useUserStore } from "@/store/user";
import { useOidcStore } from "./store/oidc";
export default defineComponent({
name: "App",
setup() {
const userStore = useUserStore();
return { userStore };
const oidcStore = useOidcStore();
const router = useRouter();
// Watch for changes to isAuthenticated flag
watch(
() => userStore.isAuthenticated,
(newValue: boolean) => {
if (!newValue) {
// If not authenticated, navigate to the login page
router.push("/login");
} else {
// If authenticated, navigate to the home page
router.push("/"); // Adjust the route name based on your routes
}
},
);
return { userStore, oidcStore };
},
methods: {
logout: async function () {
await this.userStore.logout();
async logout() {
if (this.userStore.isAuthenticated && this.userStore.authority) {
if (this.userStore.authority == "bceid") {
await this.oidcStore.logout(this.userStore.authority);
}
if (this.userStore.authority == "bcsc") {
// BCSC does not support a session logout callback endpoint so just remove session data from client
await this.oidcStore.removeUser(this.userStore.authority);
this.userStore.$reset();
}
}
},
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,12 @@ export const getClient = async (appendToken: boolean = true) => {

if (appendToken) {
const userStore = useUserStore();
const access_token = await userStore.getAccessToken();
const access_token = userStore.getAccessToken;

// Add a request interceptor to append the access token to the request
axiosClient.interceptors.request.use((config) => {
if (access_token) {
config.headers.Authorization = `Bearer ${access_token}`;
} else {
// If we've failed to get an access token from oidc-client-ts sessionStorage, clear the profile and logout
userStore.clearProfile();
}
return config;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
<template>
<main>
<button type="button" @click="handleTokenRefresh">Refresh token with BCeID</button>
<p>User: {{ profile?.display_name }}</p>
<!-- Only BCeID supports refresh token flow -->
<button v-if="userStore.getAuthority == 'bceid'" type="button" @click="handleTokenRefresh">Refresh token</button>

<p>Profile: {{ userStore.getProfile }}</p>

<p>Authority: {{ userStore.getAuthority }}</p>

<p>Access Token: {{ userStore.getAccessToken }}</p>

<Applications />
</main>
</template>

<script lang="ts">
import { mapState } from "pinia";
import { defineComponent } from "vue";
import Applications from "@/components/Applications.vue";
import { useOidcStore } from "@/store/oidc";
import { useUserStore } from "@/store/user";
export default defineComponent({
Expand All @@ -21,16 +27,17 @@ export default defineComponent({
},
setup() {
const userStore = useUserStore();
return { userStore };
},
computed: {
...mapState(useUserStore, ["profile"]),
const oidcStore = useOidcStore();
return { userStore, oidcStore };
},
methods: {
handleTokenRefresh: async function () {
const user = await this.userStore.signinSilent();
if (user?.profile) {
this.userStore.setProfile(user?.profile);
if (this.userStore.isAuthenticated && this.userStore.authority) {
const user = await this.oidcStore.signinSilent(this.userStore.authority);
if (user) {
this.userStore.setUser(user);
}
}
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,33 @@
<h1>Login</h1>

<div>
<button type="button" @click="userStore.login()">Login with BCeID</button>
<button type="button" @click="handleLogin('bceid')">Login with BCeID</button>
</div>
<div>
<button type="button" @click="handleLogin('bcsc')">Login with BCSC</button>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { useOidcStore } from "@/store/oidc";
import { useUserStore } from "@/store/user";
import type { Authority } from "@/types/authority";
export default defineComponent({
name: "Login",
setup() {
const userStore = useUserStore();
return { userStore };
const oidcStore = useOidcStore();
return { userStore, oidcStore };
},
methods: {
async handleLogin(authority: Authority) {
this.userStore.setAuthority(authority);
await this.oidcStore.login(authority);
},
},
});
</script>
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
<template><div></div></template>

<script lang="ts">
import { useOidcStore } from "@/store/oidc";
import { useUserStore } from "@/store/user";
export default {
setup() {
const userStore = useUserStore();
const oidcStore = useOidcStore();
return { userStore };
return { userStore, oidcStore };
},
mounted() {
this.handleCallback();
},
methods: {
handleCallback() {
this.userStore.completeLogout();
this.userStore.clearProfile();
this.$router.push("/");
async handleCallback() {
if (this.userStore.isAuthenticated && this.userStore.authority) {
await this.oidcStore.completeLogout(this.userStore.authority);
this.userStore.$reset();
}
},
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
<template><div></div></template>

<script lang="ts">
import { useOidcStore } from "@/store/oidc";
import { useUserStore } from "@/store/user";
export default {
setup() {
const userStore = useUserStore();
const oidcStore = useOidcStore();
return { userStore };
return { userStore, oidcStore };
},
mounted() {
this.handleCallback();
},
methods: {
async handleCallback(): Promise<void> {
const user = await this.userStore.signinCallback();
this.userStore.setProfile(user.profile);
this.$router.push("/");
if (this.userStore.authority) {
const user = await this.oidcStore.signinCallback(this.userStore.authority);
this.userStore.setUser(user);
}
},
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
<template><div></div></template>

<script lang="ts">
import { useOidcStore } from "@/store/oidc";
import { useUserStore } from "@/store/user";
export default {
setup() {
const userStore = useUserStore();
const oidcStore = useOidcStore();
return { userStore };
return { userStore, oidcStore };
},
mounted() {
this.handleCallback();
},
methods: {
handleCallback() {
this.userStore.silentCallback();
async handleCallback() {
if (this.userStore.isAuthenticated && this.userStore.authority) {
await this.oidcStore.silentCallback(this.userStore.authority);
}
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ app.use(router);

const configStore = useConfigStore();

// Fetch OIDC configuration from the API and initialize the store before mounting the app
configStore.initialize().then(() => {
app.mount("#app");
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type BaseUserManagerSettings = Pick<UserManagerSettings, "redirect_uri" | "post_
const oidcConfig: BaseUserManagerSettings = {
redirect_uri: `${window.location.origin}/signin-callback`,
post_logout_redirect_uri: `${window.location.origin}/logout-callback`,
silent_redirect_uri: `${window.location.origin}/silent-renew`,
silent_redirect_uri: `${window.location.origin}/silent-callback`,
response_type: "code",
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const useConfigStore = defineStore("config", {
applicationConfiguration: {} as Components.Schemas.ApplicationConfiguration,
}),
getters: {
oidcConfiguration: (state): UserManagerSettings => {
bceidOidcConfiguration: (state): UserManagerSettings => {
const oidc = state.applicationConfiguration?.authenticationMethods ? state.applicationConfiguration?.authenticationMethods["bceid"] : null;

const combinedConfig: UserManagerSettings = {
Expand All @@ -27,6 +27,18 @@ export const useConfigStore = defineStore("config", {
scope: oidc?.scope ?? "",
};

return combinedConfig;
},
bcscOidcConfiguration: (state): UserManagerSettings => {
const oidc = state.applicationConfiguration?.authenticationMethods ? state.applicationConfiguration?.authenticationMethods["bcsc"] : null;

const combinedConfig: UserManagerSettings = {
...oidcConfig,
client_id: oidc?.clientId ?? "",
authority: oidc?.authority ?? "",
scope: oidc?.scope ?? "",
};

return combinedConfig;
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { SignoutResponse, User } from "oidc-client-ts";
import { UserManager } from "oidc-client-ts";
import { defineStore } from "pinia";

import type { Authority } from "@/types/authority";

import { useConfigStore } from "./config";

export interface UserState {
bceidUserManager: UserManager;
bcscUserManager: UserManager;
}

export const useOidcStore = defineStore("oidc", {
state: (): UserState => ({
bceidUserManager: new UserManager(useConfigStore().bceidOidcConfiguration),
bcscUserManager: new UserManager(useConfigStore().bcscOidcConfiguration),
}),

actions: {
async login(authority: Authority): Promise<void> {
return authority === "bceid" ? await this.bceidUserManager.signinRedirect() : await this.bcscUserManager.signinRedirect();
},

async removeUser(authority: Authority): Promise<void> {
return authority === "bceid" ? await this.bceidUserManager.removeUser() : await this.bcscUserManager.removeUser();
},

async signinCallback(authority: Authority): Promise<User> {
return authority === "bceid" ? await this.bceidUserManager.signinRedirectCallback() : await this.bcscUserManager.signinRedirectCallback();
},

async silentCallback(authority: Authority): Promise<void> {
return authority === "bceid" ? await this.bceidUserManager.signinSilentCallback() : await this.bcscUserManager.signinSilentCallback();
},

async signinSilent(authority: Authority): Promise<User | null> {
return authority === "bceid" ? await this.bceidUserManager.signinSilent() : await this.bcscUserManager.signinSilent();
},

async logout(authority: Authority): Promise<void> {
return authority === "bceid" ? await this.bceidUserManager.signoutRedirect() : await this.bcscUserManager.signoutRedirect();
},

async completeLogout(authority: Authority): Promise<SignoutResponse> {
return authority === "bceid" ? await this.bceidUserManager.signoutRedirectCallback() : await this.bcscUserManager.signoutRedirectCallback();
},
},
});
Loading

0 comments on commit 517d4fa

Please sign in to comment.