Skip to content

Commit

Permalink
End OIDC sessions upstream on signout
Browse files Browse the repository at this point in the history
- Follow signout redirect/callback flow to end sessions upstream.
- Change environment configuration to set a base URL and generate
  the signin and signout redirect URLs from it.
  • Loading branch information
jraddaoui committed Jul 12, 2024
1 parent 27b772a commit 3a5fe1e
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 26 deletions.
2 changes: 1 addition & 1 deletion dashboard/.env
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
VITE_OIDC_ENABLED=\$VITE_OIDC_ENABLED
VITE_OIDC_BASE_URL=\$VITE_OIDC_BASE_URL
VITE_OIDC_AUTHORITY=\$VITE_OIDC_AUTHORITY
VITE_OIDC_CLIENT_ID=\$VITE_OIDC_CLIENT_ID
VITE_OIDC_REDIRECT_URI=\$VITE_OIDC_REDIRECT_URI
VITE_OIDC_EXTRA_SCOPES=\$VITE_OIDC_EXTRA_SCOPES
VITE_OIDC_EXTRA_QUERY_PARAMS=\$VITE_OIDC_EXTRA_QUERY_PARAMS
VITE_OIDC_ABAC_ENABLED=\$VITE_OIDC_ABAC_ENABLED
Expand Down
2 changes: 1 addition & 1 deletion dashboard/.env.development
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
VITE_OIDC_ENABLED=true
VITE_OIDC_BASE_URL=http://localhost:8080
VITE_OIDC_AUTHORITY=http://keycloak:7470/realms/artefactual
VITE_OIDC_CLIENT_ID=enduro
VITE_OIDC_REDIRECT_URI=http://localhost:8080/user/signin-callback
VITE_OIDC_EXTRA_SCOPES=enduro
VITE_OIDC_EXTRA_QUERY_PARAMS=
VITE_OIDC_ABAC_ENABLED=true
Expand Down
2 changes: 1 addition & 1 deletion dashboard/.env.test
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
VITE_OIDC_ENABLED=true
VITE_OIDC_BASE_URL=http://localhost:8080
VITE_OIDC_AUTHORITY=http://keycloak:7470/realms/artefactual
VITE_OIDC_CLIENT_ID=enduro
VITE_OIDC_REDIRECT_URI=http://localhost:8080/user/signin-callback
VITE_OIDC_EXTRA_SCOPES=enduro
VITE_OIDC_EXTRA_QUERY_PARAMS="audience=enduro-api, key = value"
VITE_OIDC_ABAC_ENABLED=true
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/Sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ onMounted(() => {
<div class="collapse" id="user-menu">
<a
class="d-block py-3 text-decoration-none text-dark sidebar-link"
@click="authStore.removeUser()"
@click="authStore.signoutRedirect()"
href="#"
>
<div class="container-fluid">
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ declare module "*.vue" {

interface ImportMetaEnv {
readonly VITE_OIDC_ENABLED: string;
readonly VITE_OIDC_BASE_URL: string;
readonly VITE_OIDC_AUTHORITY: string;
readonly VITE_OIDC_CLIENT_ID: string;
readonly VITE_OIDC_REDIRECT_URI: string;
readonly VITE_OIDC_EXTRA_SCOPES: string;
readonly VITE_OIDC_EXTRA_QUERY_PARAMS: string;
readonly VITE_OIDC_ABAC_ENABLED: string;
Expand Down
10 changes: 10 additions & 0 deletions dashboard/src/pages/user/signout-callback.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup lang="ts">
import router from "@/router";
import { useAuthStore } from "@/stores/auth";
useAuthStore()
.signoutCallback()
.then(() => router.push({ name: "/" }));
</script>

<template></template>
60 changes: 52 additions & 8 deletions dashboard/src/stores/__tests__/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ describe("useAuthStore", () => {
authStore.$patch((state) => {
state.config = {
enabled: true,
baseUrl: "",
provider: "",
clientId: "",
redirectUrl: "",
extraScopes: "",
extraQueryParams: "",
abac: {
Expand Down Expand Up @@ -187,9 +187,9 @@ describe("useAuthStore", () => {
authStore.$patch((state) => {
state.config = {
enabled: true,
baseUrl: "",
provider: "",
clientId: "",
redirectUrl: "",
extraScopes: "",
extraQueryParams: "",
abac: {
Expand All @@ -210,9 +210,9 @@ describe("useAuthStore", () => {
authStore.loadConfig();
expect(authStore.config).toEqual({
enabled: true,
baseUrl: "http://localhost:8080",
provider: "http://keycloak:7470/realms/artefactual",
clientId: "enduro",
redirectUrl: "http://localhost:8080/user/signin-callback",
extraScopes: "enduro",
extraQueryParams: "audience=enduro-api, key = value",
abac: {
Expand All @@ -235,6 +235,9 @@ describe("useAuthStore", () => {
expect(authStore.manager?.settings.redirect_uri).toEqual(
"http://localhost:8080/user/signin-callback",
);
expect(authStore.manager?.settings.post_logout_redirect_uri).toEqual(
"http://localhost:8080/user/signout-callback",
);
expect(authStore.manager?.settings.scope).toEqual(
"openid email profile enduro",
);
Expand All @@ -254,6 +257,23 @@ describe("useAuthStore", () => {
expect(authStore.manager).toEqual(null);
});

it("redirects for signin", () => {
const manager = new UserManager({
authority: "",
client_id: "",
redirect_uri: "",
});

const redirectMock = vi.fn().mockImplementation(manager.signinRedirect);
redirectMock.mockImplementation(async () => null);
manager.signinRedirect = redirectMock;

const authStore = useAuthStore();
authStore.$patch((state) => (state.manager = manager));
authStore.signinRedirect();
expect(redirectMock).toHaveBeenCalledOnce();
});

it("receives a signin callback", async () => {
const manager = new UserManager({
authority: "",
Expand All @@ -276,23 +296,47 @@ describe("useAuthStore", () => {
expect(authStore.user).toEqual(null);
});

it("redirects for signin", () => {
it("redirects for signout", () => {
const manager = new UserManager({
authority: "",
client_id: "",
redirect_uri: "",
});

const redirectMock = vi.fn().mockImplementation(manager.signinRedirect);
const redirectMock = vi.fn().mockImplementation(manager.signoutRedirect);
redirectMock.mockImplementation(async () => null);
manager.signinRedirect = redirectMock;
manager.signoutRedirect = redirectMock;

const authStore = useAuthStore();
authStore.$patch((state) => (state.manager = manager));
authStore.signinRedirect();
authStore.signoutRedirect();
expect(redirectMock).toHaveBeenCalledOnce();
});

it("receives a signout callback", async () => {
const manager = new UserManager({
authority: "",
client_id: "",
redirect_uri: "",
});

const callbackMock = vi.fn().mockImplementation(manager.signoutCallback);
callbackMock.mockImplementation(async () => null);
manager.signoutCallback = callbackMock;

const authStore = useAuthStore();
authStore.$patch((state) => (state.manager = manager));

const removeUserMock = vi.fn().mockImplementation(authStore.removeUser);
removeUserMock.mockImplementation(async () => null);
authStore.removeUser = removeUserMock;

authStore.signoutCallback();
await flushPromises();
expect(callbackMock).toHaveBeenCalledOnce();
expect(removeUserMock).toHaveBeenCalledOnce();
});

it("loads and removes the user and attributes", async () => {
var user = new User({
access_token: "",
Expand Down Expand Up @@ -518,9 +562,9 @@ describe("useAuthStore", () => {
authStore.$patch((state) => {
state.config = {
enabled: true,
baseUrl: "",
provider: "",
clientId: "",
redirectUrl: "",
extraScopes: "",
extraQueryParams: "",
abac: {
Expand Down
27 changes: 20 additions & 7 deletions dashboard/src/stores/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { Buffer } from "buffer";

type OIDCConfig = {
enabled: boolean;
baseUrl: string;
provider: string;
clientId: string;
redirectUrl: string;
extraScopes: string;
extraQueryParams: string;
abac: ABACConfig;
Expand Down Expand Up @@ -84,9 +84,9 @@ export const useAuthStore = defineStore("auth", {

this.config = {
enabled: false,
baseUrl: "",
provider: "",
clientId: "",
redirectUrl: "",
extraScopes: "",
extraQueryParams: "",
abac: {
Expand All @@ -102,15 +102,15 @@ export const useAuthStore = defineStore("auth", {
this.config.enabled =
env.VITE_OIDC_ENABLED.trim().toLowerCase() === "true";
}
if (env.VITE_OIDC_BASE_URL) {
this.config.baseUrl = env.VITE_OIDC_BASE_URL.trim();
}
if (env.VITE_OIDC_AUTHORITY) {
this.config.provider = env.VITE_OIDC_AUTHORITY.trim();
}
if (env.VITE_OIDC_CLIENT_ID) {
this.config.clientId = env.VITE_OIDC_CLIENT_ID.trim();
}
if (env.VITE_OIDC_REDIRECT_URI) {
this.config.redirectUrl = env.VITE_OIDC_REDIRECT_URI.trim();
}
if (env.VITE_OIDC_EXTRA_SCOPES) {
this.config.extraScopes = env.VITE_OIDC_EXTRA_SCOPES.trim();
}
Expand Down Expand Up @@ -158,10 +158,15 @@ export const useAuthStore = defineStore("auth", {
});
}

if (!this.config.baseUrl.endsWith("/")) {
this.config.baseUrl += "/";
}

this.manager = new UserManager({
authority: this.config.provider,
client_id: this.config.clientId,
redirect_uri: this.config.redirectUrl,
redirect_uri: this.config.baseUrl + "user/signin-callback",
post_logout_redirect_uri: this.config.baseUrl + "user/signout-callback",
extraQueryParams: extraQueryParams,
scope: scope,
userStore: new WebStorageStateStore({ store: window.localStorage }),
Expand All @@ -175,13 +180,21 @@ export const useAuthStore = defineStore("auth", {
this.loadManager();
this.setUser((await this.manager?.signinCallback()) || null);
},
signoutRedirect() {
this.loadManager();
this.manager?.signoutRedirect();
},
async signoutCallback() {
this.loadManager();
await this.manager?.signoutCallback();
await this.removeUser();
},
// Load the currently authenticated user.
async loadUser() {
this.loadManager();
this.setUser((await this.manager?.getUser()) || null);
},
async removeUser() {
// TODO: end session upstream.
this.loadManager();
await this.manager?.removeUser();
this.user = null;
Expand Down
1 change: 1 addition & 0 deletions dashboard/typed-router.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ declare module 'vue-router/auto-routes' {
'/user': RouteRecordInfo<'/user', '/user', Record<never, never>, Record<never, never>>,
'/user/signin': RouteRecordInfo<'/user/signin', '/user/signin', Record<never, never>, Record<never, never>>,
'/user/signin-callback': RouteRecordInfo<'/user/signin-callback', '/user/signin-callback', Record<never, never>, Record<never, never>>,
'/user/signout-callback': RouteRecordInfo<'/user/signout-callback', '/user/signout-callback', Record<never, never>, Record<never, never>>,
}
}
10 changes: 5 additions & 5 deletions hack/kube/base/enduro-dashboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ spec:
secretKeyRef:
name: enduro-dashboard-secret
key: oidc-enabled
- name: VITE_OIDC_BASE_URL
valueFrom:
secretKeyRef:
name: enduro-dashboard-secret
key: oidc-base-url
- name: VITE_OIDC_AUTHORITY
valueFrom:
secretKeyRef:
Expand All @@ -35,11 +40,6 @@ spec:
secretKeyRef:
name: enduro-dashboard-secret
key: oidc-client-id
- name: VITE_OIDC_REDIRECT_URI
valueFrom:
secretKeyRef:
name: enduro-dashboard-secret
key: oidc-redirect-url
- name: VITE_OIDC_EXTRA_SCOPES
valueFrom:
secretKeyRef:
Expand Down
2 changes: 1 addition & 1 deletion hack/kube/components/dev/enduro-dashboard-secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ metadata:
type: Opaque
stringData:
oidc-enabled: "true"
oidc-base-url: http://localhost:8080
oidc-provider-url: http://keycloak:7470/realms/artefactual
oidc-redirect-url: http://localhost:8080/user/signin-callback
oidc-client-id: enduro
oidc-extra-scopes: enduro
oidc-extra-query-params: ""
Expand Down
4 changes: 4 additions & 0 deletions hack/kube/components/dev/keycloak.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ data:
"enabled": true,
"publicClient": true,
"redirectUris": ["http://localhost:8080/user/signin-callback"],
"attributes": {
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "http://localhost:8080/user/signout-callback"
},
"protocol": "openid-connect",
"protocolMappers": [
{
Expand Down

0 comments on commit 3a5fe1e

Please sign in to comment.