@@ -9,6 +9,7 @@ import { Utils } from "../utils";
99import { GrantTypes } from "./../constants" ;
1010import { UnauthorizedError } from "./../errors/unauthorizedError" ;
1111import { BackendService } from "./backendService" ;
12+ import { OAuthTokenResponse } from "../contracts/oauthTokenResponse" ;
1213
1314
1415export class OAuthService {
@@ -19,6 +20,25 @@ export class OAuthService {
1920 private readonly logger : Logger
2021 ) { }
2122
23+ private async generateCodeChallenge ( codeVerifier : string ) : Promise < string > {
24+ const digest = await crypto . subtle . digest ( "SHA-256" ,
25+ new TextEncoder ( ) . encode ( codeVerifier ) ) ;
26+
27+ return btoa ( String . fromCharCode ( ...new Uint8Array ( digest ) ) )
28+ . replace ( / = / g, "" ) . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" )
29+ }
30+
31+ private generateRandomString ( length : number ) : string {
32+ let text = "" ;
33+ const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ;
34+
35+ for ( let i = 0 ; i < length ; i ++ ) {
36+ text += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) ;
37+ }
38+
39+ return text ;
40+ }
41+
2242 public async getAuthServer ( authorizationServerId : string , openidProviderId : string ) : Promise < AuthorizationServer > {
2343 try {
2444 if ( authorizationServerId ) {
@@ -58,6 +78,11 @@ export class OAuthService {
5878 accessToken = await this . authenticateCode ( backendUrl , authorizationServer ) ;
5979 break ;
6080
81+ case GrantTypes . authorizationCodeWithPkce :
82+ this . logger . trackEvent ( "TestConsoleOAuth" , { grantType : GrantTypes . authorizationCodeWithPkce } ) ;
83+ accessToken = await this . authenticateCodeWithPkce ( backendUrl , authorizationServer ) ;
84+ break ;
85+
6186 case GrantTypes . clientCredentials :
6287 this . logger . trackEvent ( "TestConsoleOAuth" , { grantType : GrantTypes . clientCredentials } ) ;
6388 accessToken = await this . authenticateClientCredentials ( backendUrl , authorizationServer , apiName ) ;
@@ -149,8 +174,9 @@ export class OAuthService {
149174 try {
150175 window . open ( oauthClient . code . getUri ( ) , "_blank" , "width=400,height=500" ) ;
151176
152- const receiveMessage = async ( event : MessageEvent ) => {
177+ const receiveMessage = async ( event : MessageEvent ) : Promise < void > => {
153178 if ( ! event . data [ "accessToken" ] ) {
179+ alert ( "Unable to authenticate due to internal error." ) ;
154180 return ;
155181 }
156182
@@ -167,6 +193,74 @@ export class OAuthService {
167193 } ) ;
168194 }
169195
196+ public async authenticateCodeWithPkce ( backendUrl : string , authorizationServer : AuthorizationServer ) : Promise < string > {
197+ const redirectUri = `${ backendUrl } /signin-oauth/code-pkce/callback/${ authorizationServer . name } ` ;
198+ const codeVerifier = this . generateRandomString ( 64 ) ;
199+ const challengeMethod = crypto . subtle ? "S256" : "plain"
200+
201+ const codeChallenge = challengeMethod === "S256"
202+ ? await this . generateCodeChallenge ( codeVerifier )
203+ : codeVerifier
204+
205+ sessionStorage . setItem ( "code_verifier" , codeVerifier ) ;
206+
207+ const args = new URLSearchParams ( {
208+ response_type : "code" ,
209+ client_id : authorizationServer . clientId ,
210+ code_challenge_method : challengeMethod ,
211+ code_challenge : codeChallenge ,
212+ redirect_uri : redirectUri ,
213+ scope : authorizationServer . scopes . join ( " " )
214+ } ) ;
215+
216+ return new Promise ( ( resolve , reject ) => {
217+ try {
218+ window . open ( authorizationServer . authorizationEndpoint + "/?" + args , "_blank" , "width=400,height=500" ) ;
219+
220+ const receiveMessage = async ( event : MessageEvent ) : Promise < void > => {
221+ const authorizationCode = event . data [ "code" ] ;
222+
223+ if ( ! authorizationCode ) {
224+ alert ( "Unable to authenticate due to internal error." ) ;
225+ return ;
226+ }
227+
228+ const body = new URLSearchParams ( {
229+ client_id : authorizationServer . clientId ,
230+ code_verifier : sessionStorage . getItem ( "code_verifier" ) ,
231+ grant_type : GrantTypes . authorizationCode ,
232+ redirect_uri : redirectUri ,
233+ code : authorizationCode
234+ } ) ;
235+
236+ const response = await this . httpClient . send < OAuthTokenResponse > ( {
237+ url : authorizationServer . tokenEndpoint ,
238+ method : HttpMethod . post ,
239+ headers : [ { name : KnownHttpHeaders . ContentType , value : KnownMimeTypes . UrlEncodedForm } ] ,
240+ body : body . toString ( )
241+ } ) ;
242+
243+ if ( response . statusCode === 400 ) {
244+ const error = response . toText ( ) ;
245+ alert ( error ) ;
246+ return ;
247+ }
248+
249+ const tokenResponse = response . toObject ( ) ;
250+ const accessToken = tokenResponse . access_token ;
251+ const accessTokenType = tokenResponse . token_type ;
252+
253+ resolve ( `${ Utils . toTitleCase ( accessTokenType ) } ${ accessToken } ` ) ;
254+ } ;
255+
256+ window . addEventListener ( "message" , receiveMessage , false ) ;
257+ }
258+ catch ( error ) {
259+ reject ( error ) ;
260+ }
261+ } ) ;
262+ }
263+
170264 /**
171265 * Acquires access token using "client credentials" grant flow.
172266 * @param backendUrl {string} Portal backend URL.
0 commit comments