11import { HoistAuthModel , managed , PlainObject , XH } from '@xh/hoist/core' ;
2- import { AuthZeroClient , AuthZeroClientConfig } from '@xh/hoist/security/authzero/AuthZeroClient' ;
2+ import { AuthZeroClient , AuthZeroClientConfig } from '@xh/hoist/security/authzero' ;
3+ import { MsalClient , MsalClientConfig } from '@xh/hoist/security/msal' ;
34
45/**
5- * Toolbox uses Auth0 for OAuth authentication. Here we are overriding the base {@link HoistAuthModel} to load
6- * OAuth-related soft-config from the Toolbox server, initialize an {@link AuthZeroClient} instance, kick-off the
7- * Oauth flow, and setup default fetch headers to include an id token.
6+ * Toolbox's implementation of {@link HoistAuthModel} contract for handling authentication.
7+ *
8+ * This example is atypical of most application implementations in that it supports a fallback
9+ * option for local username/password login, for offline or other local testing scenarios where
10+ * OAuth is undesired, as well as flows against either Auth0 (default) or Azure Entra ID.
811 */
912export class AuthModel extends HoistAuthModel {
1013 @managed
11- client : AuthZeroClient ;
14+ client : AuthZeroClient | MsalClient ;
1215
1316 override async completeAuthAsync ( ) : Promise < boolean > {
1417 this . setMaskMsg ( 'Authenticating...' ) ;
@@ -28,34 +31,19 @@ export class AuthModel extends HoistAuthModel {
2831 return ret ;
2932 }
3033
31- // Otherwise we proceed with the primary OAuth flow by constructing and initializing an AuthZeroClient, one of
32- // the OAuth implementations supported out-of-the-box by Hoist.
33- const audience = 'toolbox.xh.io' ;
34- this . client = new AuthZeroClient ( {
35- idScopes : [ 'profile' ] ,
36- // Toolbox does not actually need any access tokens -- just a test
37- accessTokens : {
38- test : {
39- scopes : [ 'profile' ] ,
40- fetchMode : 'eager' ,
41- audience
42- }
43- } ,
44- // This config works along with the accessToken requested above - by passing the same
45- // audience to our interactive login requests, they return access/refresh tokens that
46- // are immediately usable.
47- audience,
48- ...( config as AuthZeroClientConfig )
49- } ) ;
34+ // Otherwise proceed with the primary OAuth flow by constructing and initializing one of the two
35+ // supported client implementations - either Auth0 (default) or MSAL (also supported, for testing OAuth
36+ // against Microsoft Entra ID).
37+ this . client = this . createClient ( config ) ;
5038 await this . client . initAsync ( ) ;
5139
52- // With the client initialized, we tell FetchService to pass the Auth0 supplied id token (a JWT) via a custom
40+ // With the client initialized, we tell FetchService to pass the ID token (a JWT) via a custom
5341 // header on any local/relative requests going back to Toolbox Grails server.
5442 XH . fetchService . addDefaultHeaders ( async opts => {
5543 if ( opts . url . startsWith ( 'http' ) ) return null ;
5644
5745 const idToken = await this . client . getIdTokenAsync ( ) ;
58- return idToken ? { 'x-xh-idt' : idToken . value } : null ;
46+ return idToken ? { Authorization : `Bearer ${ idToken . value } ` } : null ;
5947 } ) ;
6048
6149 // Finally, make a request to check the auth-status on the server - that call will include the id token header
@@ -74,6 +62,30 @@ export class AuthModel extends HoistAuthModel {
7462 await this . client ?. logoutAsync ( ) ;
7563 }
7664
65+ private createClient ( config : PlainObject ) : AuthZeroClient | MsalClient {
66+ if ( config . provider === 'AUTH_ZERO' ) {
67+ const audience = 'toolbox.xh.io' ;
68+ return new AuthZeroClient ( {
69+ idScopes : [ 'profile' ] ,
70+ // Toolbox does not actually need any access tokens -- just a test
71+ accessTokens : {
72+ test : {
73+ scopes : [ 'profile' ] ,
74+ fetchMode : 'eager' ,
75+ audience
76+ }
77+ } ,
78+ // This config works along with the accessToken requested above - by passing the same
79+ // audience to our interactive login requests, they return access/refresh tokens that
80+ // are immediately usable.
81+ audience,
82+ ...( config as AuthZeroClientConfig )
83+ } ) ;
84+ } else {
85+ return new MsalClient ( config as MsalClientConfig ) ;
86+ }
87+ }
88+
7789 // Update overall load mask message to provide an indication that this auth flow is processing.
7890 private setMaskMsg ( msg : string ) {
7991 XH . appContainerModel . initializingLoadMaskMessage = msg ;
0 commit comments