Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keycloak npm library #2314

Merged
merged 7 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions app/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion app/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"vue-router": "^3.0.1",
"vue-select": "^3.1.0",
"vuejs-noty": "^0.1.3",
"vuex": "^3.0.1"
"vuex": "^3.0.1",
"keycloak-js": "21.1.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.7.0",
Expand Down
103 changes: 55 additions & 48 deletions app/frontend/src/common/authenticate.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,48 @@
limitations under the License.
*/
import ApiService from '@/common/services/ApiService.js'
import Keycloak from 'keycloak-js';
import Vue from 'vue'

export default {
getInstance: function () {
/**
* Returns a promise that resolves to an instance of Keycloak.
*/

return new Promise((resolve, reject) => {
if (!Vue.prototype.$keycloak) {
// Keycloak has not yet been loaded, get Keycloak configuration from the server.

ApiService.query('keycloak', {})
.then(response => {
/*
"A best practice is to load the JavaScript adapter directly from Keycloak Server as it will
automatically be updated when you upgrade the server. If you copy the adapter to your web
application instead, make sure you upgrade the adapter only after you have upgraded the server.";
source : https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter:
*/
const jsUrl = `${response.data['auth-server-url']}/js/keycloak.js`
// Inject the Keycloak javascript into the DOM.
const keyCloakScript = document.createElement('script')
keyCloakScript.onload = () => {
// Construct the Keycloak object and resolve the promise.
Vue.prototype.$keycloak = new Keycloak(response.data)
resolve(Vue.prototype.$keycloak)
}
keyCloakScript.onerror = (e) => {
// This is pretty bad - keycloak didn't load - this should never ever happen.
// There's not much we can do, so we set keycloak to a random empty object and resolve.
console.error(e)
Vue.prototype.$keycloak = {}
resolve(Vue.prototype.$keycloak)
}
keyCloakScript.async = true
keyCloakScript.setAttribute('src', jsUrl)
document.head.appendChild(keyCloakScript)

const {
'ssl-required': sslRequired,
resource,
realm,
'public-client': publicClient,
'confidential-port': confidentialPort,
clientId,
'auth-server-url': authServerUrl
} = response.data;

Vue.prototype.$keycloak = new Keycloak({
url: authServerUrl,
realm,
clientId,
sslRequired,
resource,
publicClient,
confidentialPort,
})

resolve(Vue.prototype.$keycloak)

})
.catch(error => {
console.error(error)
Vue.prototype.$keycloak = {}
reject(error)
resolve(Vue.prototype.$keycloak)
})
} else {
// Keycloak has already been loaded, so just resolve the object.
Expand Down Expand Up @@ -86,13 +88,12 @@ export default {
renewToken (instance, retries = 0) {
const maxRetries = 2

instance.updateToken(1800).success((refreshed) => {
instance.updateToken(1800).then((refreshed) => {
if (refreshed) {
this.setLocalToken(instance)
}
this.scheduleRenewal(instance)
}).error((e) => {
console.log(e)
}).catch((e) => {
// The refresh token is expired or was rejected
// we will retry after 60 sec (up to the count defined by maxRetries)
if (retries > maxRetries) {
Expand All @@ -112,27 +113,31 @@ export default {
*/
return new Promise((resolve, reject) => {
this.getInstance()
.then((instance) => {
.then(async (instance) => {
if (instance.authenticated && ApiService.hasAuthHeader() && !instance.isTokenExpired(0)) {
// We've already authenticated, have a header, and we've not expired.
resolve(instance)
} else {
// Attempt to retrieve a stored token, this may avoid us having to refresh the page.
const token = localStorage.getItem('token')
const refreshToken = localStorage.getItem('refreshToken')
const idToken = localStorage.getItem('idToken')
instance.init({
pkceMethod: 'S256',
onLoad: 'check-sso',
checkLoginIframe: true,
timeSkew: 10, // Allow for some deviation
token,
refreshToken,
idToken }
).success((result) => {

try {
// Attempt to retrieve a stored token, this may avoid us having to refresh the page.
const token = localStorage.getItem('token')
const refreshToken = localStorage.getItem('refreshToken')
const idToken = localStorage.getItem('idToken')

const authed = await instance.init({
pkceMethod: 'S256',
onLoad: 'check-sso',
timeSkew: 10,
checkLoginIframe: true,
token,
refreshToken,
idToken,
})

if (instance.authenticated) {
// We may have been authenticated, but the token could be expired.
instance.updateToken(60).success(() => {
instance.updateToken(60).then(() => {
// Store the token to avoid future round trips, and wire up the API
this.setLocalToken(instance)
// We update the store reference only after wiring up the API. (Someone might be waiting
Expand All @@ -141,7 +146,7 @@ export default {
store.commit('SET_KEYCLOAK', instance)
this.scheduleRenewal(instance)
resolve(instance)
}).error(() => {
}).catch(() => {
// The refresh token is expired or was rejected
this.removeLocalToken()
instance.clearToken()
Expand All @@ -156,9 +161,11 @@ export default {
store.commit('SET_KEYCLOAK', instance)
resolve(instance)
}
}).error((e) => {
reject(e)
})

} catch (error) {
console.error('Failed to initialize adapter:', error);
}

}
})
.catch((error) => {
Expand Down
9 changes: 6 additions & 3 deletions app/frontend/src/common/components/Auth.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ export default {
}
},
keyCloakLogin () {
this.keycloak.init().success(() => {
this.keycloak.login({ idpHint: this.config.sso_idp_hint }).success((authenticated) => {
this.keycloak.init({
checkLoginIframe: false
}).then(() => {
this.keycloak.login({ idpHint: this.config.sso_idp_hint }).then((authenticated) => {
if (authenticated) {
ApiService.authHeader('JWT', this.keycloak.token)
if (window.localStorage) {
Expand All @@ -48,7 +50,8 @@ export default {
localStorage.setItem('idToken', this.keycloak.idToken)
}
}
}).error((e) => {
}).catch((e) => {
console.error("keyCloakLogin: ", e)
this.$store.commit(SET_ERROR, { error: 'Cannot contact SSO provider' })
})
})
Expand Down
Loading