Skip to content

Commit 63cba77

Browse files
committed
feat(admin-ui): implement authorization code PKCE flow
Signed-off-by: Jeet Viramgama <[email protected]>
1 parent 0ef277d commit 63cba77

File tree

2 files changed

+133
-111
lines changed

2 files changed

+133
-111
lines changed

admin-ui/app/utils/AppAuthProvider.js

+109-111
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import React, { useState, useEffect } from 'react'
22
import ApiKeyRedirect from './ApiKeyRedirect'
33
import { useLocation } from 'react-router'
4-
import { saveState } from './TokenController'
4+
import {
5+
saveState,
6+
NoHashQueryStringUtils,
7+
saveConfigRequest,
8+
getConfigRequest,
9+
saveIssuer,
10+
getIssuer
11+
} from './TokenController'
512
import queryString from 'query-string'
613
import { uuidv4 } from './Util'
714
import { useSelector, useDispatch } from 'react-redux'
@@ -19,13 +26,16 @@ import {
1926
FetchRequestor,
2027
AuthorizationServiceConfiguration,
2128
AuthorizationRequest,
29+
TokenRequest,
2230
RedirectRequestHandler,
31+
LocalStorageBackend,
32+
DefaultCrypto,
33+
BaseTokenRequestHandler,
34+
AuthorizationNotifier,
35+
GRANT_TYPE_AUTHORIZATION_CODE,
2336
} from '@openid/appauth'
2437

25-
let configuration
26-
2738
export default function AppAuthProvider(props) {
28-
const authorizationHandler = new RedirectRequestHandler()
2939
const dispatch = useDispatch()
3040
const location = useLocation()
3141
const [showContent, setShowContent] = useState(false)
@@ -53,8 +63,8 @@ export default function AppAuthProvider(props) {
5363
const params = queryString.parse(location.search)
5464
if (!(params.code && params.scope && params.state)) {
5565
dispatch(checkLicenseConfigValid())
66+
// dispatch(getRandomChallengePair())
5667
}
57-
dispatch(getRandomChallengePair())
5868
}, [])
5969

6070
useEffect(() => {
@@ -63,120 +73,107 @@ export default function AppAuthProvider(props) {
6373
dispatch(checkLicensePresent())
6474
}
6575
}, [isConfigValid])
76+
const [error, setError] = useState(null)
77+
const [code, setCode] = useState(null)
6678

6779
useEffect(() => {
68-
getDerivedStateFromProps()
69-
}, [isLicenseValid])
80+
const authorizationHandler = new RedirectRequestHandler(
81+
new LocalStorageBackend(),
82+
new NoHashQueryStringUtils(),
83+
window.location,
84+
new DefaultCrypto()
85+
)
7086

71-
const buildAuthzUrl = (state, nonce) => {
72-
console.log('Config', config)
73-
const {
74-
authzBaseUrl,
75-
clientId,
76-
scope,
77-
redirectUrl,
78-
responseType,
79-
acrValues,
80-
} = config
81-
if (
82-
!authzBaseUrl ||
83-
!clientId ||
84-
!scope ||
85-
!redirectUrl ||
86-
!responseType ||
87-
!acrValues ||
88-
!state ||
89-
!nonce ||
90-
!codeChallenge ||
91-
!codeVerifier ||
92-
!codeChallengeMethod
93-
) {
94-
console.warn('Parameters to process authz code flow are missing.')
95-
return
96-
}
97-
return `${authzBaseUrl}?acr_values=${acrValues}&response_type=${responseType}&redirect_uri=${redirectUrl}&client_id=${clientId}&scope=${scope}&state=${state}&nonce=${nonce}&code_challenge_method=${codeChallengeMethod}&code_challenge=${codeChallenge}`
98-
}
99-
100-
const getDerivedStateFromProps = async () => {
101-
if (window.location.href.indexOf('logout') > -1) {
102-
setShowContent(true)
103-
return null
104-
}
105-
if (!isLicenseValid) {
106-
setShowContent(false)
107-
}
108-
if (!isConfigValid) {
109-
setShowContent(false)
110-
}
111-
if (!showContent) {
112-
if (!userinfo) {
113-
const params = queryString.parse(location.search)
114-
if (params.code && params.scope && params.state && !isLicenseValid) {
115-
dispatch(getUserInfo(params.code, codeVerifier))
116-
} else {
117-
if (!showContent && Object.keys(config).length) {
118-
// const state = uuidv4()
119-
// saveState(state)
120-
// const authzUrl = buildAuthzUrl(state, uuidv4())
121-
// if (authzUrl) {
122-
// window.location.href = authzUrl
123-
// }
124-
// new
125-
const state = uuidv4()
126-
saveState(state)
127-
configuration =
128-
await AuthorizationServiceConfiguration.fetchFromIssuer(
129-
issuer,
130-
new FetchRequestor()
131-
)
132-
console.log(`COMPONENT configuration`, configuration)
133-
let extras = {
134-
acr_values: config.acrValues,
135-
nonce: uuidv4(),
136-
code_challenge_method: codeChallengeMethod,
137-
code_challenge: codeChallenge,
138-
}
139-
let request = new AuthorizationRequest({
140-
client_id: config.clientId,
141-
redirect_uri: config.redirectUrl,
142-
scope: config.scope,
143-
response_type: config.responseType,
144-
state: state,
145-
extras,
146-
})
147-
console.log(`request`, request)
148-
authorizationHandler.performAuthorizationRequest(
149-
configuration,
150-
request
151-
)
152-
return
87+
if (isLicenseValid) {
88+
AuthorizationServiceConfiguration.fetchFromIssuer(
89+
issuer,
90+
new FetchRequestor()
91+
)
92+
.then((response) => {
93+
let extras = {
94+
acr_values: config.acrValues,
15395
}
154-
}
155-
setShowContent(false)
156-
return null
157-
} else {
158-
if (!userinfo.jansAdminUIRole || userinfo.jansAdminUIRole.length == 0) {
159-
setShowContent(false)
160-
setRoleNotFound(true)
161-
alert(
162-
'The logged-in user do not have valid role. Logging out of Admin UI'
96+
const authRequest = new AuthorizationRequest({
97+
client_id: config.clientId,
98+
redirect_uri: config.redirectUrl,
99+
scope: config.scope,
100+
response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
101+
state: undefined,
102+
extras,
103+
})
104+
saveIssuer(issuer)
105+
saveConfigRequest(authRequest)
106+
authorizationHandler.performAuthorizationRequest(
107+
response,
108+
authRequest
163109
)
164-
const state = uuidv4()
165-
const sessionEndpoint = `${config.endSessionEndpoint}?state=${state}&post_logout_redirect_uri=${config.postLogoutRedirectUri}`
166-
window.location.href = sessionEndpoint
167-
return null
168-
}
169-
if (!token) {
170-
dispatch(getAPIAccessToken(userinfo_jwt))
110+
})
111+
.catch((error) => {
112+
setError(error)
113+
})
114+
}
115+
}, [isLicenseValid])
116+
117+
useEffect(() => {
118+
const tokenHandler = new BaseTokenRequestHandler(new FetchRequestor())
119+
const authorizationHandler = new RedirectRequestHandler(
120+
new LocalStorageBackend(),
121+
new NoHashQueryStringUtils(),
122+
window.location,
123+
new DefaultCrypto()
124+
)
125+
const notifier = new AuthorizationNotifier()
126+
const config = getConfigRequest()
127+
const issuer = getIssuer()
128+
129+
notifier.setAuthorizationListener((request, response, error) => {
130+
console.log('the request', request)
131+
if (response) {
132+
console.log(`Authorization Code ${response.code}`)
133+
134+
let extras = null
135+
if (request.internal) {
136+
extras = {}
137+
extras.code_verifier = request.internal.code_verifier
171138
}
172-
setShowContent(true)
173-
return null
139+
140+
const tokenRequest = new TokenRequest({
141+
client_id: request.clientId,
142+
redirect_uri: request.redirectUri,
143+
grant_type: GRANT_TYPE_AUTHORIZATION_CODE,
144+
code: response.code,
145+
extras: { code_verifier: request.internal.code_verifier, scope: request.scope },
146+
})
147+
console.log(`tokenRequest`, tokenRequest)
148+
149+
AuthorizationServiceConfiguration.fetchFromIssuer(
150+
issuer,
151+
new FetchRequestor()
152+
)
153+
.then((configuration) => {
154+
return tokenHandler.performTokenRequest(configuration, tokenRequest)
155+
})
156+
.then((token) => {
157+
localStorage.setItem('access_token', token.accessToken)
158+
})
159+
.catch((oError) => {
160+
setError(oError)
161+
})
174162
}
175-
} else {
176-
setShowContent(true)
177-
return true
163+
})
164+
165+
const params = new URLSearchParams(location.search)
166+
setCode(params.get('code'))
167+
168+
if (!code) {
169+
setError('Unable to get authorization code')
170+
return
178171
}
179-
}
172+
173+
authorizationHandler.setAuthorizationNotifier(notifier)
174+
authorizationHandler.completeAuthorizationRequestIfPossible()
175+
}, [code])
176+
180177

181178
return (
182179
<React.Fragment>
@@ -208,3 +205,4 @@ export default function AppAuthProvider(props) {
208205
</React.Fragment>
209206
)
210207
}
208+

admin-ui/app/utils/TokenController.js

+24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { BasicQueryStringUtils } from "@openid/appauth"
2+
13
export const isFourZeroOneError = (error) => {
24
return error.status === 401 ? true : false
35
}
@@ -15,6 +17,22 @@ export const saveState = (state) => {
1517
}
1618
}
1719

20+
export const saveConfigRequest = (request) => {
21+
localStorage.setItem('configRequest', JSON.stringify(request))
22+
}
23+
24+
export const saveIssuer = (issuer) => {
25+
localStorage.setItem('issuer', issuer)
26+
}
27+
28+
export const getIssuer = () => {
29+
return localStorage.getItem('issuer')
30+
}
31+
32+
export const getConfigRequest = () => {
33+
return JSON.parse(localStorage.getItem('configRequest'))
34+
}
35+
1836
export const isValidState = (newState) => {
1937
return localStorage.getItem('gluu.flow.state') === newState ? true : false
2038
}
@@ -26,3 +44,9 @@ export const addAdditionalData = (audit, action, resource, payload) => {
2644
audit['payload'] = payload.action ? payload.action.action_data : {}
2745
audit['date'] = new Date()
2846
}
47+
48+
export class NoHashQueryStringUtils extends BasicQueryStringUtils {
49+
parse(input, useHash) {
50+
return super.parse(input, false);
51+
}
52+
}

0 commit comments

Comments
 (0)