Last active
April 12, 2021 06:28
-
-
Save ulrikstrid/d0a79572f9bb73af6cd33cdf9764f680 to your computer and use it in GitHub Desktop.
Msal Bindings and react component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type status = | |
| Unauthenticated | |
| Authenticating | |
| Authenticated | |
| AuthenticationError(string); | |
type state = { | |
status, | |
account: option(MsalBrowser.Account.t), | |
id_token: option(MsalBrowser.IdToken.t), | |
id_token_string: option(string), | |
access_token: option(string), | |
userState: option(string), | |
}; | |
type action = | |
| StartAuth | |
| ClearAuth | |
| SetAccount(MsalBrowser.Account.t) | |
| SetResponse(MsalBrowser.authResponse) | |
| SetError(string); | |
[@react.component] | |
let make = | |
( | |
~clientId, | |
~scopes, | |
~authority="https://login.microsoftonline.com/common", | |
~redirectUri, | |
~userState=?, | |
~loadingComponent=?, | |
~extraQueryParameters=?, | |
~prompt="select_account", | |
~children, | |
) => { | |
let (agentApp, setAgentApp) = | |
React.useState(() => { | |
let agentConfig: MsalBrowser.PublicClientApplication.config = { | |
auth: { | |
clientId, | |
redirectUri, | |
authority, | |
navigateToLoginRequestUrl: true, | |
}, | |
cache: { | |
cacheLocation: "sessionStorage", | |
storeAuthStateInCookie: false, | |
}, | |
}; | |
MsalBrowser.makePublicClientApplication(~config=agentConfig); | |
}); | |
let (logout, setLogout) = | |
React.useState(() => MsalBrowser.logout(agentApp)); | |
React.useEffect3( | |
() => { | |
let agentConfig: MsalBrowser.PublicClientApplication.config = { | |
auth: { | |
clientId, | |
redirectUri, | |
authority, | |
navigateToLoginRequestUrl: true, | |
}, | |
cache: { | |
cacheLocation: "sessionStorage", | |
storeAuthStateInCookie: false, | |
}, | |
}; | |
let agentApp = | |
MsalBrowser.makePublicClientApplication(~config=agentConfig); | |
setLogout(_ => MsalBrowser.logout(agentApp)); | |
setAgentApp(_ => agentApp); | |
None; | |
}, | |
(clientId, redirectUri, authority), | |
); | |
let (state, send) = | |
React.useReducer( | |
(state, action) => { | |
switch (action) { | |
| StartAuth => {...state, status: Authenticating} | |
| SetAccount(account) => { | |
...state, | |
account: Some(account), | |
status: Authenticated, | |
} | |
| SetResponse(response) => { | |
id_token: Some(response.idTokenClaims), | |
id_token_string: Some(response.idToken), | |
access_token: Some(response.accessToken), | |
userState: Some(response.accountState), | |
account: Some(response.account), | |
status: Authenticated, | |
} | |
| ClearAuth => {...state, status: Unauthenticated} | |
| SetError(message) => { | |
...state, | |
status: AuthenticationError(message), | |
} | |
} | |
}, | |
{ | |
status: Unauthenticated, | |
id_token: None, | |
id_token_string: None, | |
account: None, | |
access_token: None, | |
userState, | |
}, | |
); | |
React.useEffect1( | |
() => { | |
let authCallback = (response: Js.Nullable.t(MsalBrowser.authResponse)) => { | |
switch (response->Js.Nullable.toOption) { | |
| Some(response) => | |
send(SetResponse(response)); | |
Js.Promise.resolve(); | |
| None => | |
MsalBrowser.getAllAccounts(agentApp)->Belt.Array.get(0) | |
|> ( | |
fun | |
| Some(account) => send(SetAccount(account)) | |
| None => () | |
); | |
Js.Promise.resolve(); | |
}; | |
}; | |
let _ = | |
MsalBrowser.handleRedirectPromise(agentApp) | |
|> Js.Promise.then_(authCallback) | |
|> Js.Promise.catch(e => { | |
[%log.error "AAD Auth redirect"; ("error", e)]; | |
send(SetError(Obj.magic(e))); | |
Js.Promise.resolve(); | |
}); | |
None; | |
}, | |
[||], | |
); | |
React.useEffect3( | |
() => { | |
if (Belt.Option.isSome(state.account)) { | |
if (state.status == Authenticated | |
&& Belt.Option.isNone(state.access_token)) { | |
let config: MsalBrowser.acquireTokenSilentConfig = { | |
scopes, | |
account: state.account->Belt.Option.getExn, | |
authority: Some(authority), | |
claims: None, | |
forceRefresh: Some(false), | |
correlationId: None, | |
}; | |
MsalBrowser.acquireTokenSilent(agentApp, ~config) | |
|> Js.Promise.then_((accessTokenResponse: MsalBrowser.authResponse) => { | |
send(SetResponse(accessTokenResponse)); | |
Js.Promise.resolve(); | |
}) | |
|> Js.Promise.catch(e => { | |
[%log.error "AAD Auth acquireTokenSilent"; ("error", e)]; | |
send(StartAuth); | |
let loginRequest: MsalBrowser.loginRequest = { | |
scopes, | |
redirectUri, | |
prompt, | |
state: userState, | |
authority: Some(authority), | |
extraQueryParameters, | |
}; | |
let () = MsalBrowser.loginRedirect(agentApp, ~loginRequest); | |
Js.Promise.resolve(); | |
}) | |
|> ignore; | |
}; | |
}; | |
None; | |
}, | |
(state.account, state.status, state.access_token), | |
); | |
let loading = | |
Belt.Option.getWithDefault( | |
loadingComponent, | |
<p> {React.string("Loading")} </p>, | |
); | |
switch (state.status, state.id_token, state.id_token_string, state.account) { | |
| ( | |
Authenticated, | |
Some(token_claims), | |
Some(id_token_string), | |
Some(account), | |
) => | |
let config: MsalBrowser.acquireTokenSilentConfig = { | |
scopes, | |
account, | |
authority: Some(authority), | |
claims: None, | |
forceRefresh: Some(true), | |
correlationId: None, | |
}; | |
children( | |
~accessToken=state.access_token, | |
~token_claims, | |
~id_token_string, | |
~account, | |
~userState=state.userState, | |
~logout, | |
~acquireToken=() => | |
MsalBrowser.acquireTokenSilent(agentApp, ~config) | |
|> Js.Promise.then_((accessTokenResponse: MsalBrowser.authResponse) => { | |
send(SetResponse(accessTokenResponse)); | |
Js.Promise.resolve(accessTokenResponse.idToken); | |
}) | |
); | |
| (AuthenticationError(message), _, _, _) => | |
[%log.error "AuthenticationError"; ("message", message)]; | |
<p> {React.string(Js.Json.stringify(Obj.magic(message)))} </p>; | |
| (Authenticated, _, _, _) => loading | |
| (Authenticating, _, _, _) => loading | |
| (Unauthenticated, _, _, _) => | |
<StartSignIn | |
onClick={_ => { | |
send(StartAuth); | |
let loginRequest: MsalBrowser.loginRequest = { | |
scopes, | |
redirectUri, | |
prompt, | |
state: userState, | |
authority: Some(authority), | |
extraQueryParameters, | |
}; | |
MsalBrowser.loginRedirect(agentApp, ~loginRequest); | |
}} | |
/> | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module IdToken = { | |
type t = { | |
[@bs.as "exp"] | |
expiration: float, | |
homeObjectId: string, | |
issuer: string, | |
name: string, | |
objectId: string, | |
preferredName: string, | |
rawIdToken: string, | |
subject: string, | |
[@bs.as "tid"] | |
tentantId: string, | |
version: string, | |
}; | |
}; | |
module Account = { | |
type t = { | |
accountIdentifier: string, | |
homeAccountIdentifier: string, | |
userName: string, | |
name: string, | |
idToken: IdToken.t, | |
sid: string, | |
environment: string, | |
}; | |
}; | |
type logLevel; | |
type logLevels = { | |
[@bs.as "Error"] | |
error: logLevel, | |
[@bs.as "Info"] | |
info: logLevel, | |
[@bs.as "Verbose"] | |
verbose: logLevel, | |
[@bs.as "Warning"] | |
warning: logLevel, | |
}; | |
[@bs.module "@azure/msal-browser"] external logLevel: logLevels = "LogLevel"; | |
type logger; | |
type loggerConfig = { | |
level: logLevel, | |
correlationId: string, | |
}; | |
[@bs.new] [@bs.module "@azure/msal-browser"] | |
external makeLogger: ('cb, loggerConfig) => logger = "Logger"; | |
/* applicationConfig */ | |
type config = { | |
clientID: string, | |
graphScopes: array(string), | |
}; | |
type authCallback = | |
( | |
~errorDesc: Js.Nullable.t(string), | |
~token: string, | |
~error: string, | |
~tokenType: string, | |
~userState: string | |
) => | |
unit; | |
type cacheLocation = [ | `localStorage | `sessionStorage]; | |
type authResponse = { | |
uniqueId: string, | |
tenantId: string, | |
tokenType: string, | |
idToken: string, | |
idTokenClaims: IdToken.t, | |
accessToken: string, | |
scopes: array(string), | |
expiresOn: Js.Date.t, | |
account: Account.t, | |
accountState: string, | |
}; | |
module PublicClientApplication = { | |
type authConfig = { | |
clientId: string, | |
authority: string, | |
redirectUri: string, | |
navigateToLoginRequestUrl: bool, | |
}; | |
type cacheConfig = { | |
cacheLocation: string, | |
storeAuthStateInCookie: bool, | |
}; | |
type config = { | |
auth: authConfig, | |
cache: cacheConfig, | |
}; | |
type publicClientApplication('a) = { | |
cacheLocation, | |
loginInProgress: bool, | |
}; | |
}; | |
[@bs.new] [@bs.module "@azure/msal-browser"] | |
external makePublicClientApplication: | |
(~config: PublicClientApplication.config) => | |
PublicClientApplication.publicClientApplication('a) = | |
"PublicClientApplication"; | |
type loginRequest = { | |
scopes: array(string), | |
redirectUri: string, | |
authority: option(string), | |
state: option(string), | |
prompt: string, | |
extraQueryParameters: option(Js.Dict.t(string)), | |
}; | |
[@bs.send] | |
external loginPopup: | |
( | |
PublicClientApplication.publicClientApplication('a), | |
~loginRequest: loginRequest | |
) => | |
Js.Promise.t(string) = | |
"loginPopup"; | |
[@bs.send] | |
external loginRedirect: | |
( | |
PublicClientApplication.publicClientApplication('a), | |
~loginRequest: loginRequest | |
) => | |
unit = | |
"loginRedirect"; | |
[@bs.send] | |
external loginInProgress: | |
PublicClientApplication.publicClientApplication('a) => bool = | |
"loginInProgress"; | |
[@bs.send] | |
external logout: | |
(PublicClientApplication.publicClientApplication('a), unit) => unit = | |
"logout"; | |
type acquireTokenSilentConfig = { | |
account: Account.t, | |
scopes: array(string), | |
claims: option(string), | |
authority: option(string), | |
forceRefresh: option(bool), | |
correlationId: option(string), | |
}; | |
[@bs.send] | |
external acquireTokenSilent: | |
( | |
PublicClientApplication.publicClientApplication('a), | |
~config: acquireTokenSilentConfig | |
) => | |
Js.Promise.t(authResponse) = | |
"acquireTokenSilent"; | |
[@bs.send] | |
external acquireTokenPopup: | |
(PublicClientApplication.publicClientApplication('a), array(string)) => | |
Js.Promise.t(string) = | |
"aquireTokenPopup"; | |
[@bs.send] | |
external handleRedirectPromise: | |
PublicClientApplication.publicClientApplication('a) => | |
Js.Promise.t(Js.Nullable.t(authResponse)) = | |
"handleRedirectPromise"; | |
[@bs.send] | |
external getAllAccounts: | |
PublicClientApplication.publicClientApplication('a) => array(Account.t) = | |
"getAllAccounts"; | |
let internal_getCacheItem = (cacheLocation, itemKey) => | |
switch (cacheLocation) { | |
| `localStorage => Storage.localStorage->(Storage.getItem(itemKey)) | |
| `sessionStorage => Storage.sessionStorage->(Storage.getItem(itemKey)) | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<MsalAuth | |
clientId="<client_id>" | |
scopes=[|"openid", "profile", "email"|] | |
redirectUri={Utils.getRedirectUrl()} | |
userState={Utils.getCurrentUrl()} | |
loadingComponent={<LoadingComponent />} | |
authority="https://login.microsoftonline.com/common" | |
prompt="select_account"> | |
...{( | |
~accessToken as _, | |
~token_claims: MsalBrowser.IdToken.t, | |
~id_token_string as token, | |
~account as _, | |
~userState as _, | |
~logout, | |
~acquireToken, | |
) => { | |
... | |
}} | |
</MsalAuth> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment