├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── lerna.json ├── package-lock.json ├── package.json ├── packages └── react-aad-msal │ ├── .auto-changelog │ ├── .eslintrc.js │ ├── .gitignore │ ├── .huskyrc.json │ ├── .lintstagedrc.json │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── LICENSE │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── AccessTokenResponse.ts │ ├── AuthenticationActionCreators.ts │ ├── IdTokenResponse.ts │ ├── Logger.ts │ ├── MsalAuthProvider.ts │ ├── components │ │ ├── AzureAD.tsx │ │ └── withAuthentication.tsx │ ├── enums │ │ ├── AuthenticationActions.ts │ │ ├── AuthenticationState.ts │ │ ├── LoginType.ts │ │ ├── TokenType.ts │ │ └── index.ts │ ├── index.test.tsx │ ├── index.ts │ └── interfaces │ │ ├── IAccountInfo.ts │ │ ├── IAuthProvider.ts │ │ ├── IMsalAuthProviderConfig.ts │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── samples ├── react-javascript │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── auth.html │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── GetAccessTokenButton.js │ │ ├── GetIdTokenButton.js │ │ ├── SampleAppButtonLaunch.js │ │ ├── SampleAppRedirectOnLaunch.js │ │ ├── authProvider.js │ │ ├── index.css │ │ ├── index.js │ │ ├── reduxStore.js │ │ └── registerServiceWorker.js │ └── yarn.lock └── react-typescript │ ├── package.json │ ├── public │ ├── auth.html │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── GetAccessTokenButton.tsx │ ├── GetIdTokenButton.tsx │ ├── SampleAppButtonLaunch.tsx │ ├── SampleAppRedirectOnLaunch.tsx │ ├── SampleBox.tsx │ ├── authProvider.ts │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── reduxStore.ts │ ├── serviceWorker.ts │ └── setupTests.ts │ └── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Library versions 28 | - `react-aad-msal`: [e.g. version number] 29 | - `msal`: [e.g. version number] 30 | 31 | ### Mention any other details that might be useful 32 | 33 | > --------------------------------------------------------------- 34 | > Thanks! We'll be in touch soon. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Library versions** 11 | - `react-aad-msal`: [e.g. version number] 12 | - `msal`: [e.g. version number] 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | 21 | **To Reproduce** 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | 28 | **Desktop (please complete the following information):** 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | 157 | # Uncomment if necessary however generally it will be regenerated when needed 158 | #!**/packages/repositories.config 159 | # NuGet v3's project.json files produces more ignoreable files 160 | *.nuget.props 161 | *.nuget.targets 162 | 163 | # Microsoft Azure Build Output 164 | csx/ 165 | *.build.csdef 166 | 167 | # Microsoft Azure Emulator 168 | ecf/ 169 | rcf/ 170 | 171 | # Windows Store app package directories and files 172 | AppPackages/ 173 | BundleArtifacts/ 174 | Package.StoreAssociation.xml 175 | _pkginfo.txt 176 | 177 | # Visual Studio cache files 178 | # files ending in .cache can be ignored 179 | *.[Cc]ache 180 | # but keep track of directories ending in .cache 181 | !*.[Cc]ache/ 182 | 183 | # Others 184 | ClientBin/ 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # Since there are multiple workflows, uncomment next line to ignore bower_components 195 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 196 | #bower_components/ 197 | 198 | # RIA/Silverlight projects 199 | Generated_Code/ 200 | 201 | # Backup & report files from converting an old project file 202 | # to a newer Visual Studio version. Backup files are not needed, 203 | # because we have git ;-) 204 | _UpgradeReport_Files/ 205 | Backup*/ 206 | UpgradeLog*.XML 207 | UpgradeLog*.htm 208 | 209 | # SQL Server files 210 | *.mdf 211 | *.ldf 212 | 213 | # Business Intelligence projects 214 | *.rdl.data 215 | *.bim.layout 216 | *.bim_*.settings 217 | 218 | # Microsoft Fakes 219 | FakesAssemblies/ 220 | 221 | # GhostDoc plugin setting file 222 | *.GhostDoc.xml 223 | 224 | # Node.js Tools for Visual Studio 225 | .ntvs_analysis.dat 226 | 227 | # Visual Studio 6 build log 228 | *.plg 229 | 230 | # Visual Studio 6 workspace options file 231 | *.opt 232 | 233 | # Visual Studio LightSwitch build output 234 | **/*.HTMLClient/GeneratedArtifacts 235 | **/*.DesktopClient/GeneratedArtifacts 236 | **/*.DesktopClient/ModelManifest.xml 237 | **/*.Server/GeneratedArtifacts 238 | **/*.Server/ModelManifest.xml 239 | _Pvt_Extensions 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | paket-files/ 244 | 245 | # FAKE - F# Make 246 | .fake/ 247 | 248 | # JetBrains Rider 249 | .idea/ 250 | *.sln.iml 251 | 252 | # Library specific ignore files 253 | node_modules/ 254 | *.tgz 255 | *.zip 256 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/node_modules": true, 4 | "debug.log": true 5 | }, 6 | "typescript.tsdk": "node_modules\\typescript\\lib", 7 | "[javascript]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | "editor.formatOnSave": true 10 | }, 11 | "[javascriptreact]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode", 13 | "editor.formatOnSave": true 14 | }, 15 | "[typescript]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode", 17 | "editor.formatOnSave": true 18 | }, 19 | "[typescriptreact]": { 20 | "editor.defaultFormatter": "esbenp.prettier-vscode", 21 | "editor.formatOnSave": true 22 | }, 23 | "editor.codeActionsOnSave": { 24 | "source.fixAll.eslint": true 25 | }, 26 | "eslint.validate": [ 27 | "html", 28 | "javascript", 29 | "typescript", 30 | "javascriptreact", 31 | "typescriptreact" 32 | ], 33 | "eslint.workingDirectories": [ 34 | {"pattern": "./packages/*/"}, 35 | {"pattern": "./samples/*/"} 36 | ], 37 | } 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to React AAD MSAL 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 6 | 7 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Developing the Module 57 | If you want to test a change you added to the module, start the sample app with `npm run start-dev`. This will build the module and start the sample app without running the linter. 58 | 59 | ### Submitting a Pull Request (PR) 60 | Before you submit your Pull Request (PR) consider the following guidelines: 61 | 62 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 63 | that relates to your submission. You don't want to duplicate effort. 64 | 65 | * Make your changes in a new git fork: 66 | 67 | * Commit your changes using a descriptive commit message 68 | * Push your fork to GitHub: 69 | * In GitHub, create a pull request 70 | * If we suggest changes then: 71 | * Make the required updates. 72 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 73 | 74 | ```shell 75 | git rebase master -i 76 | git push -f 77 | ``` 78 | 79 | That's it! Thank you for your contribution! 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 React AAD MSAL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **DEPRECATION NOTICE: Microsoft has released the official [`@azure/msal-react`](https://www.npmjs.com/package/@azure/msal-react) library which replaces `react-aad-msal`. It is recommended that applications migrate to the officially supported library in order to utilize the better Auth Code Flow process. `react-aad-msal` uses older MSAL dependencies which have known drawbacks for some clients. New applications should not consider using this library, as it will not be maintained. Please review the `@azure/msal-react` official [documentation](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react), [samples](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react#samples), and [migration guide](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/migration-guide.md) for more information.** 2 | 3 |

4 | 5 |

6 |

The easiest way to integrate the Microsoft Authentication Library (MSAL) with React applications. Secure your apps with Azure Active Directory (AAD)

7 |

8 | npm version build status dependencies npm 9 |

10 | 11 |

:monkey_face: Our code monkeys live on a solid diet of :star:'s. If you like what they're doing, please feed them!

12 | 13 | # React AAD MSAL 14 | 15 | A library of components to easily integrate the Microsoft Authentication Library with Azure Active Directory in your React app quickly and reliably. The library focuses on flexibility, providing functionality to login, logout, and fetch the user details while maintaining access to the underlying MSAL library for advanced use. 16 | 17 | **:exclamation: This library is not affiliated with the Identity team at Microsoft. It was developed as a tool for the Open Source community to use and contribute to as they see fit.** 18 | 19 | ## :memo: Table of contents 20 | 21 | - [React AAD MSAL](#react-aad-msal) 22 | - [:memo: Table of contents](#memo-table-of-contents) 23 | - [:tada: Features](#tada-features) 24 | - [:checkered_flag: Getting Started](#checkeredflag-getting-started) 25 | - [Prerequisites](#prerequisites) 26 | - [Installation](#installation) 27 | - [Creating the Provider](#creating-the-provider) 28 | - [Msal Configuration](#msal-configuration) 29 | - [Authentication Parameters](#authentication-parameters) 30 | - [Options](#options) 31 | - [:package: Authentication Components](#package-authentication-components) 32 | - [AzureAD Component](#azuread-component) 33 | - [Higher Order Component](#higher-order-component) 34 | - [:mortar_board: Advanced Topics](#mortarboard-advanced-topics) 35 | - [Getting Tokens for API Requests](#getting-tokens-for-api-requests) 36 | - [Refreshing Access Tokens](#refreshing-access-tokens) 37 | - [Renewing IdTokens](#renewing-idtokens) 38 | - [Integrating with a Redux Store](#integrating-with-a-redux-store) 39 | - [Accessing the MSAL API](#accessing-the-msal-api) 40 | - [:cd: Sample applications](#cd-sample-applications) 41 | - [:calendar: Roadmap](#calendar-roadmap) 42 | - [:books: Resources](#books-resources) 43 | - [:trophy: Contributors](#trophy-contributors) 44 | 45 | ## :tada: Features 46 | 47 | :white_check_mark: Login/logout with `AzureAD` component 48 | :white_check_mark: Callback functions for login success, logout success, and user info changed 49 | :white_check_mark: `withAuthentication` higher order component for protecting components, routes, or the whole app 50 | :white_check_mark: Function as Child Component pattern ([FaCC](https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9)) to pass authentication data and login/logout functions to children components 51 | :white_check_mark: Redux integration for storing authentication status, user info, tokens, etc 52 | :white_check_mark: Automatic renewal of IdTokens, and optional function to get a fresh token at any point 53 | :white_check_mark: Easily fetch a fresh Access Token from cache (or refresh it) before calling API endpoints 54 | :white_check_mark: Various build types including ES6, CommonJS, and UMD 55 | 56 | ## :checkered_flag: Getting Started 57 | 58 | ### Prerequisites 59 | 60 | - [node.js](https://nodejs.org/en/) 61 | - [Register an app](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) in AzureAD to get a `clientId`. You will need to [follow additional steps](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration) to enable the app for your SPA site. 62 | 63 | ### Installation 64 | 65 | Via NPM: 66 | 67 | ``` 68 | npm install react-aad-msal msal --save 69 | ``` 70 | 71 | ### Creating the Provider 72 | 73 | Before beginning it is required to configure an instance of the `MsalAuthProvider` and give it three parameters: 74 | 75 | | Parameters | Description | 76 | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 77 | | `config` | Instance of a `Msal.Configuration` object to configure the underlying provider. The documentation for all the options can be found in the [configuration options](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-js-initializing-client-applications#configuration-options) doc | 78 | | `parameters` | Instance of the `Msal.AuthenticationParameters` configuration to identify how the authentication process should function. This object includes the `scopes` values. You can see possible [values for scopes here](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-scopes) | 79 | | `options` | **[Optional]** The `options` are defined by the [IMsalAuthProviderConfig](/packages/react-aad-msal/src/interfaces/IMsalAuthProviderConfig.ts) interface. This contains settings that describe how the authentication provider should handle certain authentication processes. | 80 | 81 | The `MsalAuthProvider` is meant to be a singleton. There are known implications when multiple instances of MSAL are running at the same time. The recommended approach is to instantiate the `MsalAuthProvider` in a separate file and `import` it when needed. 82 | 83 | ```TypeScript 84 | // authProvider.js 85 | import { MsalAuthProvider, LoginType } from 'react-aad-msal'; 86 | 87 | // Msal Configurations 88 | const config = { 89 | auth: { 90 | authority: 'https://login.microsoftonline.com/common', 91 | clientId: '', 92 | redirectUri: '' 93 | }, 94 | cache: { 95 | cacheLocation: "localStorage", 96 | storeAuthStateInCookie: true 97 | } 98 | }; 99 | 100 | // Authentication Parameters 101 | const authenticationParameters = { 102 | scopes: [ 103 | '', 104 | 'https://.onmicrosoft.com//' 105 | ] 106 | } 107 | 108 | // Options 109 | const options = { 110 | loginType: LoginType.Popup, 111 | tokenRefreshUri: window.location.origin + '/auth.html' 112 | } 113 | 114 | export const authProvider = new MsalAuthProvider(config, authenticationParameters, options) 115 | ``` 116 | 117 | Now you can `import` the `authProvider` and use it in combination with one of the authentication components. 118 | 119 | ```tsx 120 | // index.js 121 | import React from 'react'; 122 | import ReactDOM from 'react-dom'; 123 | import { AzureAD } from 'react-aad-msal'; 124 | 125 | import App from './App'; 126 | import { authProvider } from './authProvider'; 127 | 128 | ReactDOM.render( 129 | 130 | 131 | , 132 | document.getElementById('root'), 133 | ); 134 | ``` 135 | 136 | #### `Msal` Configuration 137 | 138 | The options that get passed to the `MsalAuthProvider` are defined by the MSAL library, and are described in more detail in the [configuration options](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-js-initializing-client-applications#configuration-options) documentation. 139 | 140 | Below is the total set of configurable options that are supported currently in the `config`. 141 | 142 | ```TypeScript 143 | // Configuration Object 144 | export type Configuration = { 145 | auth: AuthOptions, 146 | cache?: CacheOptions, 147 | system?: SystemOptions 148 | }; 149 | 150 | // Protocol Support 151 | export type AuthOptions = { 152 | clientId: string; 153 | authority?: string; 154 | validateAuthority?: boolean; 155 | redirectUri?: string | (() => string); 156 | postLogoutRedirectUri?: string | (() => string); 157 | navigateToLoginRequestUrl?: boolean; 158 | }; 159 | 160 | // Cache Support 161 | export type CacheOptions = { 162 | cacheLocation?: CacheLocation; 163 | storeAuthStateInCookie?: boolean; 164 | }; 165 | 166 | // Library support 167 | export type SystemOptions = { 168 | logger?: Logger; 169 | loadFrameTimeout?: number; 170 | tokenRenewalOffsetSeconds?: number; 171 | navigateFrameWait?: number; 172 | }; 173 | ``` 174 | 175 | For more information on MSAL config options refer to the MSAL [configuration options](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-js-initializing-client-applications) documentation. 176 | 177 | #### Authentication Parameters 178 | 179 | When instantiating an instance of the `MsalAuthProvider` the authentication parameters passed will become the default parameters used when authenticating and fetching or refreshing tokens. It is possible to change the default parameters later by executing the `setAuthenticationParameters()` method on the `MsalAuthProvider`. 180 | 181 | The set of options that are supported for the `Msal.AuthenticationParameters` class can be found below as they are defined in the MSAL library. 182 | 183 | ```TypeScript 184 | export type AuthenticationParameters = { 185 | scopes?: Array; 186 | extraScopesToConsent?: Array; 187 | prompt?: string; 188 | extraQueryParameters?: {[key: string]: string}; 189 | claimsRequest?: string; 190 | authority?: string; 191 | state?: string; 192 | correlationId?: string; 193 | account?: Account; 194 | sid?: string; 195 | loginHint?: string; 196 | }; 197 | ``` 198 | 199 | #### Options 200 | 201 | The `options` parameter defines settings related to how the authentication provider processes authentication operations provided by the `MSAL` library. 202 | 203 | ```typescript 204 | const options = { 205 | // The default login type is Popup 206 | loginType: LoginType.Popup, 207 | // A blank html page that MSAL can load in an iframe to refresh the token 208 | // The default setting for this parameter is `window.location.origin` 209 | tokenRefreshUri: window.location.origin + '/auth.html', 210 | }; 211 | ``` 212 | 213 | `LoginType` is an enum with two options for `Popup` or `Redirect` authentication. This parameter is optional and will default to `Popup` if not provided. The `tokenRefreshUri` allows you to set a separate page to load only when tokens are being refreshed. When `MSAL` attempts to refresh a token, it will reload the page in an iframe. This option allows you to inform `MSAL` of a specific page it can load in the iframe. It is best practice to use a blank HTML file so as to prevent all your site scripts and contents from loading multiple times. 214 | 215 | At any time after instantiating the `MsalAuthProvider` the login type can be changed using the `setProviderOptions()` method. You may also retrieve the current options using the `getProviderOptions()` method. 216 | 217 | ## :package: Authentication Components 218 | 219 | The library provides multiple components to integrate Azure AD authentication into your application and each component has various use cases. The are also plans for additional components, documented in the project [Roadmap](#calendar-roadmap). 220 | 221 | ### AzureAD Component 222 | 223 | The `AzureAD` component is the primary method to add authentication to your application. When the component is loaded it internally uses MSAL to check the cache and determine the current authentication state. The users authentication status determines how the component will render the `children`. 224 | 225 | 1. If the `children` is an element, it will only be rendered when the `AzureAD` detects an authenticated user. 226 | 2. If the `children` is a function, then it will always be executed with the following argument: 227 | ```tsx 228 | { 229 | login, // login function 230 | logout, // logout function 231 | authenticationState, // the current authentication state 232 | error, // any error that occurred during the login process 233 | accountInfo, // account info of the authenticated user 234 | } 235 | ``` 236 | 237 | The `AzureAD` component will check that the IdToken is not expired before determining that the user is authenticated. If the token has expired, it will attempt to renew it silently. If a valid token is maintained it will be sure there is an active Access Token available, otherwise it will refresh silently. If either of the tokens cannot be refreshed without user interaction, the user will be prompted to signin again. 238 | 239 | ```tsx 240 | import { AzureAD, AuthenticationState, AuthenticationState } from 'react-aad-msal'; 241 | 242 | // Import the provider created in a different file 243 | import { authProvider } from './authProvider'; 244 | 245 | // Only authenticated users can see the span, unauthenticated users will see nothing 246 | 247 | Only authenticated users can see me. 248 | 249 | 250 | // If the user is not authenticated, login will be initiated and they will see the span when done 251 | 252 | Only authenticated users can see me. 253 | 254 | 255 | // Using a function inside the component will give you control of what to show for each state 256 | 257 | { 258 | ({login, logout, authenticationState, error, accountInfo}) => { 259 | switch (authenticationState) { 260 | case AuthenticationState.Authenticated: 261 | return ( 262 |

263 | Welcome, {accountInfo.account.name}! 264 | 265 |

266 | ); 267 | case AuthenticationState.Unauthenticated: 268 | return ( 269 |
270 | {error &&

An error occurred during authentication, please try again!

} 271 |

272 | Hey stranger, you look new! 273 | 274 |

275 |
276 | ); 277 | case AuthenticationState.InProgress: 278 | return (

Authenticating...

); 279 | } 280 | } 281 | } 282 |
283 | ``` 284 | 285 | The following props are available to the `AzureAD` component. 286 | 287 | | AzureAD Props | Description | 288 | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 289 | | `provider` | An `MsalAuthProvider` instance containing configuration values for your Azure AD instance. See [Creating the Provider](#Creating the Provider) for details. | 290 | | `authenticatedFunction` | :warning: **[Deprecated]** **[Optional]** A user defined callback function for the AzureAD component to consume. This function receives the AzureAD components `logout function` which you can use to trigger a logout. The return value will be rendered by the AzureAD component. If no return value is provided, the `children` of the AzureAD component will be rendered instead. | 291 | | `unauthenticatedFunction` | :warning: **[Deprecated]** **[Optional]** A user defined callback function for the AzureAD component to consume. This function receives the AzureAD components `login function` which you can then use to trigger a login. The return value will be rendered by the AzureAD component. | 292 | | `accountInfoCallback` | :warning: **[Deprecated]** **[Optional]** A user defined callback function for the AzureAD component to consume. The AzureAD component will call this function when login is complete to pass back the user info as an instance of [`IAccountInfo`](/packages/react-aad-msal/src/interfaces/IAccountInfo.ts). In addition to the tokens, the account info includes the [`Msal.Account`](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/src/Account.ts) | 293 | | `reduxStore` | **[Optional]** You can provide a redux store which the component will dispatch actions to when changes occur. You can find the list of all actions defined in the [AuthenticationActions](src/actions.ts) enum. | 294 | | `forceLogin` | **[Optional]** A boolean that identifies whether the login process should be invoked immediately if the current user is unauthenticated. Defaults to `false`. | 295 | 296 | ### Higher Order Component 297 | 298 | Sometimes it's easier to utilize a Higher Order Component (HoC) to lock down an app or page component with authentication. This can be accomplished using the `withAuthentication` component which acts as a wrapper for the `AzureAD` component. 299 | 300 | ```Typescript 301 | import { withAuthentication } from 'react-aad-msal'; 302 | 303 | // The instance of MsalAuthProvider defined in a separate file 304 | import { authProvider } from './authProvider.js'; 305 | 306 | // The App component wrapped in the withAuthentication() HoC 307 | export default withAuthentication(App, { 308 | provider: authProvider, 309 | reduxStore: store 310 | }); 311 | ``` 312 | 313 | The first parameter is the component that requires authentication before being mounted. The second parameter is an object containing the props to be passed into the `AzureAD` component. With this approach the `forceLogin` prop will default to true. This is a good way to protect routes or quickly require authentication for your entire `App` in several lines. 314 | 315 | ## :mortar_board: Advanced Topics 316 | 317 | ### Getting Tokens for API Requests 318 | 319 | The library components will manage authenticating the user without you needing to think about tokens. But there are scenarios where a fresh token will be needed to communicate with a service or to decode the token to examine the claims. The library exposes methods for retrieving active IdTokens and Access Tokens. 320 | 321 | For more advanced scenarios where you need specific control over error handling when a token fails to renew you can always [access the MSAL API](#accessing-the-msal-api) methods and renew a token manually as described in the MSAL [token renewal pattern](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs#q3-how-to-renew-tokens-with-msaljs) documentation. 322 | 323 | #### Refreshing Access Tokens 324 | 325 | To get a fresh and valid Access Token to pass to an API you can call the `getAccessToken()` on the `MsalAuthProvider` instance. This function will asynchronously attempt to retrieve the token from the cache. If the cached token has expired it will automatically attempt to refresh it. In some scenarios the token refresh will fail and the user will be required to authenticate again before a fresh token is provided. The method will handle these scenarios automatically. 326 | 327 | The `getAccessToken()` returns an instance of the [`AccessTokenResponse`](/packages/react-aad-msal/src/AccessTokenResponse.ts) class. The following snippet is an example of how you might use the function before calling an API endpoint. 328 | 329 | ```typescript 330 | // authProvider.js 331 | import { MsalAuthProvider, LoginType } from 'react-aad-msal'; 332 | export const authProvider = new MsalAuthProvider( 333 | { 334 | /* Msal configurations */ 335 | }, 336 | { 337 | /* Authentication parameters */ 338 | }, 339 | { 340 | /* Options */ 341 | }, 342 | ); 343 | 344 | // api.js 345 | import { authProvider } from './authProvider'; 346 | 347 | const request = async url => { 348 | const token = await authProvider.getAccessToken(); 349 | 350 | return fetch(url, { 351 | method: 'GET', 352 | headers: { 353 | Authorization: 'Bearer ' + token.accessToken, 354 | 'Content-Type': 'application/json', 355 | }, 356 | }); 357 | }; 358 | ``` 359 | 360 | #### Renewing IdTokens 361 | 362 | To get a fresh and valid IdToken you can call the `getIdToken()` on the `MsalAuthProvider` instance. This function will asynchronously attempt to retrieve the token from the cache. If the cached token has expired it will automatically attempt to renew it. In some scenarios the token renewal will fail and the user will be required to authenticate again before a new token is provided. The method will handle these scenarios automatically. 363 | 364 | The `getIdToken()` returns an instance of the [`IdTokenResponse`](/packages/react-aad-msal/src/IdTokenResponse.ts) class. The following snippet is an example of how you might use the function to retrieve a valid IdToken. 365 | 366 | ```typescript 367 | // authProvider.js 368 | import { MsalAuthProvider, LoginType } from 'react-aad-msal'; 369 | export const authProvider = new MsalAuthProvider( 370 | { 371 | /* Msal configurations */ 372 | }, 373 | { 374 | /* Authentication parameters */ 375 | }, 376 | { 377 | /* Options */ 378 | }, 379 | ); 380 | 381 | // consumer.js 382 | import { authProvider } from './authProvider'; 383 | const token = await authProvider.getIdToken(); 384 | const idToken = token.idToken.rawIdToken; 385 | ``` 386 | 387 | ### Integrating with a Redux Store 388 | 389 | The `AzureAD` component optionally accepts a `reduxStore` prop. On successful login and after an Access Token has been acquired, an action of type `AAD_LOGIN_SUCCESS` will be dispatch to the provided store containing the token and user information returned from Active Directory. It does the same for logout events, but the action will not contain a payload. 390 | 391 | Import your store into the file rendering the `AzureAD` component and pass it as a prop: 392 | 393 | ```jsx 394 | // authProvider.js 395 | import { MsalAuthProvider, LoginType } from 'react-aad-msal'; 396 | export const authProvider = new MsalAuthProvider( 397 | { 398 | /* Msal configurations */ 399 | }, 400 | { 401 | /* Authentication parameters */ 402 | }, 403 | { 404 | /* Options */ 405 | }, 406 | ); 407 | 408 | // index.js 409 | import { authProvider } from './authProvider'; 410 | import { store } from './reduxStore.js'; 411 | 412 | // ... 413 | 414 | ReactDOM.render( 415 | 416 | 417 | , 418 | document.getElementById('root'), 419 | ); 420 | ``` 421 | 422 | Add a case to handle `AAD_LOGIN_SUCCESS` and `AAD_LOGOUT_SUCCESS` actions in a reducer file: 423 | 424 | ```javascript 425 | const initialState = { 426 | aadResponse: null, 427 | }; 428 | 429 | const sampleReducer = (state = initialState, action) => { 430 | switch (action.type) { 431 | case 'AAD_LOGIN_SUCCESS': 432 | return { ...state, aadResponse: action.payload }; 433 | case 'AAD_LOGOUT_SUCCESS': 434 | return { ...state, aadResponse: null }; 435 | default: 436 | return state; 437 | } 438 | }; 439 | ``` 440 | 441 | In addition to login and logout actions, the `MsalAuthProvider` will dispatch other actions for various state changes. The full list can be found in the [AuthenticationActions.ts](/packages/react-aad-msal/src/enums/AuthenticationActions.ts) file and in the table below. An example of a fully implemented [sample reducer](/samples/react-javascript/src/reduxStore.js) can be found in the [sample project](/sample/). 442 | 443 | | Action Type | Payload | Description | 444 | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 445 | | AAD_INITIALIZING | None | Dispatched when the `MsalAuthProvider` is instantiated and begins checking the cache to determine if the user is authenticated | 446 | | AAD_INITIALIZED | None | Signifies that the `MsalAuthProvider` has successfully determined the authentication status of the user after being instantiated. It is safest not to use any state until initialization has completed | 447 | | AAD_AUTHENTICATED_STATE_CHANGED | [`AuthenticationState`](/packages/react-aad-msal/src/enums/AuthenticationState.ts) | Dispatched whenever the user's authentication status changes | 448 | | AAD_ACQUIRED_ID_TOKEN_SUCCESS | [`IdTokenResponse`](/packages/react-aad-msal/src/IdTokenResponse.ts) | Identifies that the IdToken has been retrieved or renewed successfully | 449 | | AAD_ACQUIRED_ID_TOKEN_ERROR | [`Msal.AuthError`](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/src/error/AuthError.ts) | Dispatched when an error occurred while attempting to retrieve or renew the IdToken | 450 | | AAD_ACQUIRED_ACCESS_TOKEN_SUCCESS | [`AccessTokenResponse`](/packages/react-aad-msal/src/AccessTokenResponse.ts) | Identifies that the Access Token has been retrieved or refreshed successfully | 451 | | AAD_ACQUIRED_ACCESS_TOKEN_ERROR | [`Msal.AuthError`](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/src/error/AuthError.ts) | Dispatched when an error occurred while attempting to retrieve or refresh the Access Token | 452 | | AAD_LOGIN_SUCCESS | [`IAccountInfo`](/packages/react-aad-msal/src/interfaces/IAccountInfo.ts) | Dispatched when the user has been authenticated and a valid Access Token has been acquired | 453 | | AAD_LOGIN_FAILED | None | Dispatched when the authentication process fails | 454 | | AAD_LOGIN_ERROR | [`Msal.AuthError`](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/src/error/AuthError.ts) | Identifies that an error occurred while login was in process | 455 | | AAD_LOGOUT_SUCCESS | None | Dispatched when the user has successfully logged out on the client side | 456 | 457 | ### Accessing the MSAL API 458 | 459 | While this wrapper attempts to provide a full-featured means of authenticating with AzureAD, for advanced cases you may want to access the underlying MSAL API. The `MsalAuthProvider` extends the MSAL `UserAgentApplication` class and will give you access to all the functions available, in addition to implementing new methods used by the library components. 460 | 461 | ```jsx 462 | const authProvider = new MsalAuthProvider(config, authenticationParameters); 463 | // authProvider provides access to MSAL features 464 | ``` 465 | 466 | It is not recommended to use this method if it can be avoided, since operations executed via MSAL may not reflect in the wrapper. 467 | 468 | ## :cd: Sample applications 469 | 470 | If you'd like to see a sample application running please see the [samples](samples/) applications, built with Create React App. 471 | 472 | The project can be built with the following steps: 473 | 474 | 1. `git clone https://github.com/syncweek-react-aad/react-aad.git` 475 | 2. `cd ./react-aad` 476 | 3. Install the dependencies: 477 | - `yarn install` 478 | 4. Build the `react-aad` library: 479 | - `yarn build` 480 | 5. Run the default sample project from the project root using `yarn start`, or navigate to the sample folder you want and use `yarn start` directly. 481 | 482 | Be sure to modify the [authProvider.js](/samples/react-javascript/src/authProvider.js) configuration to specify your own `ClientID` and `Authority`. 483 | 484 | ## :calendar: Roadmap 485 | 486 | While the library is ready for use there is still plenty of ongoing work. The following is a list of a few of the improvements under consideration. 487 | 488 | :white_medium_small_square: Rewrite the sample app to use hooks and simplify the logic. 489 | :white_medium_small_square: Add a typescript sample application. 490 | :white_medium_small_square: Add a `useAuthentication()` hook to the library. 491 | :white_medium_small_square: Replace the `AzureAD` render props with event handlers. 492 | :white_medium_small_square: Add Context API provider. 493 | :white_medium_small_square: Add samples for consuming a Web API. 494 | :white_medium_small_square: Improve unit test coverage across the library. 495 | :white_medium_small_square: Maintain feature parity between the official MSAL [Angular library](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-angular) after it undergoes its planned upgrade. 496 | 497 | ## :books: Resources 498 | 499 | The following resources may be helpful and provide further insight. If you've written a blog post, tutorial, or article feel free to create an issue so we can include it. 500 | 501 | - [Get Started with Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/get-started-azure-ad) 502 | - [MSAL Documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/AzureAD/microsoft-authentication-library-for-js/dev/docs/index.html) 503 | - [AAD v2 Scopes](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-scopes) 504 | - [AAD B2C Setup MSA App](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-setup-msa-app) 505 | 506 | ## :trophy: Contributors 507 | 508 | This library is built with :heart: by members of the open source community. To become a contributor, please see the [contribution guidelines](CONTRIBUTING.md). 509 | 510 | [![](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/images/0)](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/links/0)[![](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/images/1)](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/links/1)[![](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/images/2)](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/links/2)[![](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/images/3)](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/links/3)[![](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/images/4)](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/links/4)[![](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/images/5)](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/links/5)[![](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/images/6)](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/links/6)[![](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/images/7)](https://sourcerer.io/fame/AndrewCraswell/syncweek-react-aad/react-aad/links/7) 511 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*", "samples/*", "toolkits/*"], 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "version": "independent" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "lerna": "^3.19.0" 5 | }, 6 | "workspaces": { 7 | "packages": [ 8 | "packages/*", 9 | "samples/*" 10 | ], 11 | "nohoist": [ 12 | "**/eslint" 13 | ] 14 | }, 15 | "scripts": { 16 | "start": "yarn workspace react-aad-msal start", 17 | "lint": "yarn workspace react-aad-msal lint", 18 | "test": "yarn workspace react-aad-msal test", 19 | "build": "yarn workspace react-aad-msal build" 20 | }, 21 | "dependencies": {}, 22 | "husky": { 23 | "hooks": { 24 | "pre-commit": "lerna run --concurrency 1 --stream precommit" 25 | } 26 | }, 27 | "version": "0.0.1" 28 | } 29 | -------------------------------------------------------------------------------- /packages/react-aad-msal/.auto-changelog: -------------------------------------------------------------------------------- 1 | { 2 | "output": "CHANGELOG.md", 3 | "template": "keepachangelog", 4 | "unreleased": true, 5 | "commitLimit": false 6 | } -------------------------------------------------------------------------------- /packages/react-aad-msal/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:jsx-a11y/recommended', 8 | 'plugin:jsdoc/recommended', 9 | 'prettier', 10 | 'prettier/@typescript-eslint', 11 | ], 12 | env: { 13 | browser: true, 14 | es6: true, 15 | node: true, 16 | }, 17 | settings: { 18 | react: { 19 | version: 'detect', 20 | }, 21 | jsdoc: { 22 | mode: 'typescript', 23 | }, 24 | }, 25 | parser: '@typescript-eslint/parser', 26 | parserOptions: { 27 | sourceType: 'module', 28 | ecmaFeatures: { 29 | jsx: true, 30 | modules: true, 31 | }, 32 | }, 33 | ignorePatterns: ['node_modules/', 'dist/'], 34 | plugins: ['@typescript-eslint', 'react', 'react-hooks', 'jsx-a11y', 'jsdoc'], 35 | rules: { 36 | '@typescript-eslint/member-ordering': 'warn', 37 | '@typescript-eslint/no-explicit-any': 'off', 38 | '@typescript-eslint/interface-name-prefix': ['error', { prefixWithI: 'always' }], 39 | '@typescript-eslint/explicit-function-return-type': 'off', 40 | '@typescript-eslint/no-non-null-assertion': 'off', 41 | '@typescript-eslint/no-use-before-define': 'off', 42 | '@typescript-eslint/unbound-method': 'off', 43 | '@typescript-eslint/camelcase': 'off', 44 | 'react/no-unescaped-entities': 'off', 45 | 'react/forbid-dom-props': ['error', { forbid: ['style'] }], 46 | 'react/prop-types': 'off', 47 | 'react/jsx-no-bind': 'error', 48 | 'react-hooks/rules-of-hooks': 'error', 49 | 'react-hooks/exhaustive-deps': 'off', 50 | 'jsdoc/check-tag-names': [ 51 | 'warn', 52 | { 53 | definedTags: ['typeparam', 'jest-environment'], 54 | }, 55 | ], 56 | 'jsdoc/require-returns': 'off', 57 | 'jsdoc/require-param-type': 'off', 58 | camelcase: 'warn', 59 | 'id-match': 'warn', 60 | 'max-classes-per-file': 'off', 61 | 'no-underscore-dangle': 'off', 62 | 'no-console': 'error', 63 | 'no-warning-comments': 'warn', 64 | 'no-case-declarations': 'off', 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /packages/react-aad-msal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # node 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | dist 11 | 12 | # misc 13 | .DS_Store 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # intellij config 19 | .idea 20 | 21 | -------------------------------------------------------------------------------- /packages/react-aad-msal/.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged", 4 | "pre-push": "npm run lint:types" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-aad-msal/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,json,html,yaml,md}": ["prettier --write", "git add"], 3 | "*.{ts,tsx}": ["prettier --write", "eslint --fix", "git add"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/react-aad-msal/.prettierignore: -------------------------------------------------------------------------------- 1 | /dist 2 | -------------------------------------------------------------------------------- /packages/react-aad-msal/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "all", 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /packages/react-aad-msal/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 9 | 10 | ## [v2.3.5](https://github.com/syncweek-react-aad/react-aad/compare/v0.3.7...v2.3.5) - 2020-04-18 11 | 12 | ### Merged 13 | 14 | - Removing domain_hint parameter from silent calls as it is not required when executing acquire token silent [`#220`](https://github.com/syncweek-react-aad/react-aad/pull/220) 15 | - Updated Package Json [`#224`](https://github.com/syncweek-react-aad/react-aad/pull/224) 16 | - Fixed Typo [`#226`](https://github.com/syncweek-react-aad/react-aad/pull/226) 17 | - fix import using baseUrl [`#205`](https://github.com/syncweek-react-aad/react-aad/pull/205) 18 | - added typescript sample with all hooks [`#204`](https://github.com/syncweek-react-aad/react-aad/pull/204) 19 | - Resolve referential issues, and pass correct params to loginToRefreshToken [`#202`](https://github.com/syncweek-react-aad/react-aad/pull/202) 20 | - Using input auth params if provided when user interaction is required while retrieving access token [`#200`](https://github.com/syncweek-react-aad/react-aad/pull/200) 21 | - Update msal version to 1.2.1 [`#196`](https://github.com/syncweek-react-aad/react-aad/pull/196) 22 | - Fix reference issue to enums [`#194`](https://github.com/syncweek-react-aad/react-aad/pull/194) 23 | - Modify the documentation for the new monorepo strucutre [`#191`](https://github.com/syncweek-react-aad/react-aad/pull/191) 24 | - Monorepo [`#190`](https://github.com/syncweek-react-aad/react-aad/pull/190) 25 | - Fix path in package.main [`#184`](https://github.com/syncweek-react-aad/react-aad/pull/184) 26 | - Fix pacakge.json main field to point to the CommonJS build so TypeScript users can utilize typings [`#182`](https://github.com/syncweek-react-aad/react-aad/pull/182) 27 | - Fix non-function children of AzureAD from rendering when authentication is InProgres [`#181`](https://github.com/syncweek-react-aad/react-aad/pull/181) 28 | - Fix wrong auth parameters being used when renewing tokens [`#180`](https://github.com/syncweek-react-aad/react-aad/pull/180) 29 | - Andcra/fix auth params [`#179`](https://github.com/syncweek-react-aad/react-aad/pull/179) 30 | - Produce ES6 modules, ES5 CommonJS, and UMD builds [`#175`](https://github.com/syncweek-react-aad/react-aad/pull/175) 31 | - Hotfix: infinite authentication loop when forceLogin is true [`#174`](https://github.com/syncweek-react-aad/react-aad/pull/174) 32 | - Andcra/fix promise error [`#173`](https://github.com/syncweek-react-aad/react-aad/pull/173) 33 | - Expose the IMsalAuthProfiderConfig interface [`#172`](https://github.com/syncweek-react-aad/react-aad/pull/172) 34 | - Update to MSAL version 1.2.0 and fix breaking changes [`#170`](https://github.com/syncweek-react-aad/react-aad/pull/170) 35 | - Remove exports from MSAL peerDependency [`#168`](https://github.com/syncweek-react-aad/react-aad/pull/168) 36 | - Bump the version to 2.0.0 in preparation for new release [`#167`](https://github.com/syncweek-react-aad/react-aad/pull/167) 37 | - Add a new authentication state, InProgress [`#166`](https://github.com/syncweek-react-aad/react-aad/pull/166) 38 | - Make MSAL a peer dependency so clients can choose which version to use [`#163`](https://github.com/syncweek-react-aad/react-aad/pull/163) 39 | - Fix infinite loop of login process when forceLogin is true and an error occurs [`#165`](https://github.com/syncweek-react-aad/react-aad/pull/165) 40 | - Update MSAL to the latest version [`#162`](https://github.com/syncweek-react-aad/react-aad/pull/162) 41 | - Update MSAL to beta 3 [`#161`](https://github.com/syncweek-react-aad/react-aad/pull/161) 42 | - Set displayName of components for better rendering in React DevTools [`#157`](https://github.com/syncweek-react-aad/react-aad/pull/157) 43 | - No longer automatically fetch Access Token on login [`#156`](https://github.com/syncweek-react-aad/react-aad/pull/156) 44 | - Update MSAL dependency [`#153`](https://github.com/syncweek-react-aad/react-aad/pull/153) 45 | - upgrade react-scripts to 3.2.0 (minor upgrade) [`#146`](https://github.com/syncweek-react-aad/react-aad/pull/146) 46 | - remove react/react-dom dependency from sample project [`#144`](https://github.com/syncweek-react-aad/react-aad/pull/144) 47 | - Feature: catch authentication errors [`#140`](https://github.com/syncweek-react-aad/react-aad/pull/140) 48 | - don't lint dist files (even if staged) [`#142`](https://github.com/syncweek-react-aad/react-aad/pull/142) 49 | - Fix typos in README.md [`#136`](https://github.com/syncweek-react-aad/react-aad/pull/136) 50 | - Export IAzureADFunctionProps [`#138`](https://github.com/syncweek-react-aad/react-aad/pull/138) 51 | - Update Access Token Refresh Docs [`#135`](https://github.com/syncweek-react-aad/react-aad/pull/135) 52 | - Update README.md [`#130`](https://github.com/syncweek-react-aad/react-aad/pull/130) 53 | - Andcra/readme edits [`#127`](https://github.com/syncweek-react-aad/react-aad/pull/127) 54 | - Update the README to match the v1.0.0 changes [`#125`](https://github.com/syncweek-react-aad/react-aad/pull/125) 55 | - Add Function as a Child Component pattern to the AzureAD component for easier use [`#126`](https://github.com/syncweek-react-aad/react-aad/pull/126) 56 | - Attempt to refresh IdToken if it has expired [`#123`](https://github.com/syncweek-react-aad/react-aad/pull/123) 57 | - Clean the project folder / remove leftover traces of Create React App [`#121`](https://github.com/syncweek-react-aad/react-aad/pull/121) 58 | - Add ability to get a valid/refreshed token for API request [`#119`](https://github.com/syncweek-react-aad/react-aad/pull/119) 59 | - Fix bug where UserAgentApplication is not being exposed properly [`#114`](https://github.com/syncweek-react-aad/react-aad/pull/114) 60 | - Fix spelling mistake in README [`#112`](https://github.com/syncweek-react-aad/react-aad/pull/112) 61 | - Expose the full AuthResponse details [`#107`](https://github.com/syncweek-react-aad/react-aad/pull/107) 62 | - Add the withAuthentication Higher-Order Component. Resolves #108. [`#111`](https://github.com/syncweek-react-aad/react-aad/pull/111) 63 | - Fix issues described in #102 where Redirection auth is not properly setting the token, and not storing in Redux until page refresh [`#110`](https://github.com/syncweek-react-aad/react-aad/pull/110) 64 | - Fix sample code casing [`#101`](https://github.com/syncweek-react-aad/react-aad/pull/101) 65 | - Wrong enum type for redirect in sample [`#96`](https://github.com/syncweek-react-aad/react-aad/pull/96) 66 | - Call redux store first and then change state [`#94`](https://github.com/syncweek-react-aad/react-aad/pull/94) 67 | - Update MSAL to v1.0.1 [`#93`](https://github.com/syncweek-react-aad/react-aad/pull/93) 68 | - Fix broken sample app. Correct sample app documentation. [`#91`](https://github.com/syncweek-react-aad/react-aad/pull/91) 69 | - Update README.md [`#87`](https://github.com/syncweek-react-aad/react-aad/pull/87) 70 | - Resolves #83, adding a forceLogin parameter to the AzureAD component [`#84`](https://github.com/syncweek-react-aad/react-aad/pull/84) 71 | - Add changelog [`#82`](https://github.com/syncweek-react-aad/react-aad/pull/82) 72 | - Fix edge case where AzureAD render() function can return void [`#81`](https://github.com/syncweek-react-aad/react-aad/pull/81) 73 | - Get latest from Master [`#3`](https://github.com/syncweek-react-aad/react-aad/pull/3) 74 | - Fix unauthenticated callback being called even after login [`#80`](https://github.com/syncweek-react-aad/react-aad/pull/80) 75 | - Merge latest from master [`#2`](https://github.com/syncweek-react-aad/react-aad/pull/2) 76 | - Adding prettier and repo badges [`#78`](https://github.com/syncweek-react-aad/react-aad/pull/78) 77 | - Switch to using commonjs [`#77`](https://github.com/syncweek-react-aad/react-aad/pull/77) 78 | - Clean up dev dependencies [`#76`](https://github.com/syncweek-react-aad/react-aad/pull/76) 79 | - Generate type definition files as part of build [`#75`](https://github.com/syncweek-react-aad/react-aad/pull/75) 80 | - Update dependencies to resolve security vulnerabilities, and enable new MSAL config options [`#70`](https://github.com/syncweek-react-aad/react-aad/pull/70) 81 | - Merge with latest from base project [`#1`](https://github.com/syncweek-react-aad/react-aad/pull/1) 82 | - Clean up and replace travis with azure pipelines [`#73`](https://github.com/syncweek-react-aad/react-aad/pull/73) 83 | - Added option for redirctUri to authProvider [`#56`](https://github.com/syncweek-react-aad/react-aad/pull/56) 84 | - Update README.md with disclaimer [`#60`](https://github.com/syncweek-react-aad/react-aad/pull/60) 85 | - Changing the order that dispatch and authentiationState are handled [Issue #53] [`#57`](https://github.com/syncweek-react-aad/react-aad/pull/57) 86 | - Move AzureAD component to its own file [`#51`](https://github.com/syncweek-react-aad/react-aad/pull/51) 87 | - Refactor MSAL-specific configuration options into seperate prop [`#50`](https://github.com/syncweek-react-aad/react-aad/pull/50) 88 | - Exporting AAD_LOGOUT_SUCCESS [Issue #48] [`#49`](https://github.com/syncweek-react-aad/react-aad/pull/49) 89 | - Refactor MSAL code out of AzureAD Component [`#45`](https://github.com/syncweek-react-aad/react-aad/pull/45) 90 | - update to use GitHub credentials instead of VSTS [`#43`](https://github.com/syncweek-react-aad/react-aad/pull/43) 91 | - feat(login persistence) component recognizes when a user is logged in after refresh [`#41`](https://github.com/syncweek-react-aad/react-aad/pull/41) 92 | - Need to force bump version to fix deploy [`#40`](https://github.com/syncweek-react-aad/react-aad/pull/40) 93 | - Fix travis3 [`#34`](https://github.com/syncweek-react-aad/react-aad/pull/34) 94 | - Updating README.md [`#36`](https://github.com/syncweek-react-aad/react-aad/pull/36) 95 | - fix travis build [`#33`](https://github.com/syncweek-react-aad/react-aad/pull/33) 96 | 97 | ### Fixed 98 | 99 | - Add the withAuthentication Higher-Order Component. Resolves #108. (#111) [`#108`](https://github.com/syncweek-react-aad/react-aad/issues/108) [`#108`](https://github.com/syncweek-react-aad/react-aad/issues/108) 100 | - Fix broken sample app. Correct sample app documentation. (#91) [`#88`](https://github.com/syncweek-react-aad/react-aad/issues/88) 101 | - Resolves #83, adding a forceLogin parameter to the AzureAD component (#84) [`#83`](https://github.com/syncweek-react-aad/react-aad/issues/83) 102 | - Fix #69 where the UnauthenticatedCallback gets executed even after logging in. [`#69`](https://github.com/syncweek-react-aad/react-aad/issues/69) 103 | - Fixed a few references to the old npm rimraf script. Also fixes #26 where use of the npm --prefix flag was cusing scripts to install to ./sample folder [`#26`](https://github.com/syncweek-react-aad/react-aad/issues/26) 104 | - Fixes #74 by generating type definition files as part of build output [`#74`](https://github.com/syncweek-react-aad/react-aad/issues/74) 105 | 106 | ### Commits 107 | 108 | - Update NPM dependencies to fix all detected security vulnerabilities, and enable all new MSAL options to be used. [`cc53b06`](https://github.com/syncweek-react-aad/react-aad/commit/cc53b06513e92601545cea80636ef9cf364914f0) 109 | - Remove package.lock.json to allow for merge [`08eb5ff`](https://github.com/syncweek-react-aad/react-aad/commit/08eb5ffd5a3ca753ba688c87bb4ca3d3373352d5) 110 | - Update react-dom npm and address PR comment [`d621de8`](https://github.com/syncweek-react-aad/react-aad/commit/d621de879402fe1d7cde5b4f66f2d073200b2f16) 111 | - Adding prettier, format files, and badges [`3a8b42e`](https://github.com/syncweek-react-aad/react-aad/commit/3a8b42eda16f142c9dd55cf0ad21ab7f4d7dac55) 112 | - Fix unit tests [`b2259b8`](https://github.com/syncweek-react-aad/react-aad/commit/b2259b828eb8415887548b0467d7d38230e4a9d5) 113 | - Male all the callback props on the AzureAD component optional [`23824c1`](https://github.com/syncweek-react-aad/react-aad/commit/23824c1476a296d64863b06ca2b777e380a71fd4) 114 | - update script and travis build [`599279e`](https://github.com/syncweek-react-aad/react-aad/commit/599279ea64eb73b9bf4162bb78d2dcb653098853) 115 | - Update issue templates [`31cc5d7`](https://github.com/syncweek-react-aad/react-aad/commit/31cc5d75e6dafe40ee12576947be1e6190124cc6) 116 | - Add linter changes [`1b750bf`](https://github.com/syncweek-react-aad/react-aad/commit/1b750bf7c19c4f5a4d8540f7292da8167f88430f) 117 | - Simplify authentication state and user recieved callbacks [`6e25f32`](https://github.com/syncweek-react-aad/react-aad/commit/6e25f32646db65312ac435d488494a607354a5f6) 118 | - fixing build when push vs pr [`df1f666`](https://github.com/syncweek-react-aad/react-aad/commit/df1f6664eb810601f1597243143692d54e65e591) 119 | - Remove ts-jest config from package.json, as we rely on babel-jest to transpile TypeScript [`c6e6401`](https://github.com/syncweek-react-aad/react-aad/commit/c6e64014b005338ecb159d5c0332eae1baa899b2) 120 | - Fix readme formatting [`93e318e`](https://github.com/syncweek-react-aad/react-aad/commit/93e318e9ef9ae40c3eb5381ea63e3b68b46a9ca5) 121 | - FIx edge case where if the authenticatedFunction or unauthenticatedFunction don't return a value, React will throw excetions expecting render() to result in an element or null return value [`f9761e9`](https://github.com/syncweek-react-aad/react-aad/commit/f9761e9ef053c8f9085662a28987e1b7f3874a12) 122 | - Add missing markdown [`f4f040e`](https://github.com/syncweek-react-aad/react-aad/commit/f4f040ec5f5e240db12063b7e883e982f1d46c00) 123 | - Export the IUSerInfo interface so that TypeScript projects can strong type the user type [`03b7236`](https://github.com/syncweek-react-aad/react-aad/commit/03b72368da20c1c41133603856a3e005baa1a084) 124 | - Merge latest from Master [`51cdd32`](https://github.com/syncweek-react-aad/react-aad/commit/51cdd3215686a7a57f2f408c17652c13128c3ea4) 125 | - Complete merge conflict [`f2a88f9`](https://github.com/syncweek-react-aad/react-aad/commit/f2a88f9020740f1a836e22fdde9ce44481f8b633) 126 | - Merge latest with master [`2b993c6`](https://github.com/syncweek-react-aad/react-aad/commit/2b993c611566550d02d21a25d80bb389d537d2d7) 127 | - Merge with latest CI changes from Master [`b20bf1f`](https://github.com/syncweek-react-aad/react-aad/commit/b20bf1fb755f4f04f7bc54fcf015562de79d96d1) 128 | - Update .travis.yml [`43fed94`](https://github.com/syncweek-react-aad/react-aad/commit/43fed94b251efd41b9ce8d3bb2f2761b63ece1ee) 129 | - Exporting AAD_LOGOUT_SUCCESS [`a105c37`](https://github.com/syncweek-react-aad/react-aad/commit/a105c372921e56e9c8128b0851c1479c47931184) 130 | - Changing the orderthat dispatch and authentiationState are handled [`c957938`](https://github.com/syncweek-react-aad/react-aad/commit/c9579384967f6ecde9eed0f96adfd2d0b791ffe7) 131 | - merge from master [`76edeac`](https://github.com/syncweek-react-aad/react-aad/commit/76edeac6ac9d5677518a2e2d7e8c542cb658b53b) 132 | - try travis update [`ea2f8df`](https://github.com/syncweek-react-aad/react-aad/commit/ea2f8df2bb1539ec2a3a2099f482460be95d6ff5) 133 | 134 | ## v0.3.7 - 2018-05-14 135 | 136 | ### Merged 137 | 138 | - remove clean, corrected skip ci to ci skip [`#30`](https://github.com/syncweek-react-aad/react-aad/pull/30) 139 | - Updating Travis Settings [`#28`](https://github.com/syncweek-react-aad/react-aad/pull/28) 140 | - added buttons to switch between samples [Issue #9] [`#24`](https://github.com/syncweek-react-aad/react-aad/pull/24) 141 | - enabling travis build [`#25`](https://github.com/syncweek-react-aad/react-aad/pull/25) 142 | - Removed use of symlink and pointed to react-aad-msal module through [Issue #15 & #18] [`#22`](https://github.com/syncweek-react-aad/react-aad/pull/22) 143 | - Adding AAD_LOGOUT_SUCCESS case to reduxStore.js [Issue #20] [`#21`](https://github.com/syncweek-react-aad/react-aad/pull/21) 144 | - Setting tsconfig to exclude sample folder [Issue #10] [`#17`](https://github.com/syncweek-react-aad/react-aad/pull/17) 145 | - Updating READMEs [Issue #11] [`#16`](https://github.com/syncweek-react-aad/react-aad/pull/16) 146 | - added microsoft copyright statement / mit license to files [`#14`](https://github.com/syncweek-react-aad/react-aad/pull/14) 147 | - Added sample application [`#12`](https://github.com/syncweek-react-aad/react-aad/pull/12) 148 | - Fix Redirect LoginType to authenticate [`#4`](https://github.com/syncweek-react-aad/react-aad/pull/4) 149 | - Updating README to add component property details [`#2`](https://github.com/syncweek-react-aad/react-aad/pull/2) 150 | - Moving sync week project from VSTS to GH. [`#1`](https://github.com/syncweek-react-aad/react-aad/pull/1) 151 | 152 | ### Commits 153 | 154 | - Removed use of symlink and pointed to react-aad-msal module through [`fc54464`](https://github.com/syncweek-react-aad/react-aad/commit/fc544641d238f3511596ae5dc637ea070878bc39) 155 | - Added sample app [`4d3acb6`](https://github.com/syncweek-react-aad/react-aad/commit/4d3acb6ab845b2f440a3ec68d34699c732d09861) 156 | - cleaned up READMEs [`f8ebaf8`](https://github.com/syncweek-react-aad/react-aad/commit/f8ebaf804f1291fb8110f80649892f6b2c775c24) 157 | - Updating README's [`0b8dcef`](https://github.com/syncweek-react-aad/react-aad/commit/0b8dcef1f0f19c029f1ca0a87a14b3d8e8bba1f2) 158 | - Adding travis settings [`0439d39`](https://github.com/syncweek-react-aad/react-aad/commit/0439d399d54c8bba24c9d7f69f28695242cda2ae) 159 | - Initial commit [`dce2cda`](https://github.com/syncweek-react-aad/react-aad/commit/dce2cdaea7ebcd6cafc782a8801568015f54292c) 160 | - Initial commit [`f4b3bd0`](https://github.com/syncweek-react-aad/react-aad/commit/f4b3bd022b0db0700c69cfb5255bf48baa3249d0) 161 | - Initial commit [`912b8a5`](https://github.com/syncweek-react-aad/react-aad/commit/912b8a5e0ae53599c70082c38a9e134c6ea22443) 162 | - Initial commit [`46c8184`](https://github.com/syncweek-react-aad/react-aad/commit/46c8184149f3b6732ee9cc4f79030d4dcd66845b) 163 | - Updating README so that properties are in a table format [`0dd8b86`](https://github.com/syncweek-react-aad/react-aad/commit/0dd8b8610a715244fd91bf99b09f75819fc17906) 164 | - Initial commit [`0f03ec5`](https://github.com/syncweek-react-aad/react-aad/commit/0f03ec53061413431333a26808a95b1f2daf8417) 165 | - Updating README resources; updating code snippets to jsx [`1b98fa8`](https://github.com/syncweek-react-aad/react-aad/commit/1b98fa85135e18bb8cd4773040093527f8889aa3) 166 | - Initial commit [`ab0feb3`](https://github.com/syncweek-react-aad/react-aad/commit/ab0feb3200cb6f5e743c56406ae51689efc73368) 167 | - cleaned up merge issues [`77c7213`](https://github.com/syncweek-react-aad/react-aad/commit/77c721338905fc4b3b18844b71d257a1ab3955df) 168 | - Added sample app [`daa9dec`](https://github.com/syncweek-react-aad/react-aad/commit/daa9decfd3558d8c7dda45f118a3587d0396496d) 169 | - Initial commit [`69e4fae`](https://github.com/syncweek-react-aad/react-aad/commit/69e4fae425b3599dd4bf3146b0654aa3bf867f53) 170 | - add npm run start-dev to contributing guidelines [`3e69d3e`](https://github.com/syncweek-react-aad/react-aad/commit/3e69d3e18d0a6d4ef9ff12350b1cc23b3fc3e0c3) 171 | - Update README [`c47f280`](https://github.com/syncweek-react-aad/react-aad/commit/c47f28052fb1ce9293691b865520d199c734cd76) 172 | - chore: release version 0.3.7 [ci skip] [`751acba`](https://github.com/syncweek-react-aad/react-aad/commit/751acba0f48eb8529efcfca7c6b88c2c098f535c) 173 | - chore: release version 0.3.6 [ci skip] [`9a65602`](https://github.com/syncweek-react-aad/react-aad/commit/9a65602c826f815f67315c8e52c7174a87b0928b) 174 | - chore: release version 0.3.5 [skip ci] [`8f84178`](https://github.com/syncweek-react-aad/react-aad/commit/8f84178008bb1d7793b292e0997998b002e4a20f) 175 | - chore: release version 0.3.4 [skip ci] [`6724520`](https://github.com/syncweek-react-aad/react-aad/commit/6724520d8a33d08867670a8e376ff7337e8a5d31) 176 | - chore: release version 0.3.3 [skip ci] [`20630d6`](https://github.com/syncweek-react-aad/react-aad/commit/20630d634a50b02e40d342fc6b76caf89fac1f5d) 177 | - chore: release version 0.3.2 [skip ci] [`9579140`](https://github.com/syncweek-react-aad/react-aad/commit/957914067c2d495dff40f8b713014d958fc0fca5) 178 | - Adding project name to CONTRIBUTING and LICENSE files [`fb0b884`](https://github.com/syncweek-react-aad/react-aad/commit/fb0b88405d9120fdd8fbfcf66efc630e04002fec) 179 | - Update .travis.yml [`546faa7`](https://github.com/syncweek-react-aad/react-aad/commit/546faa7f3ed7d0f69bbd7c5b228feb598a04e98f) 180 | - set tags to false, add master branch to npm deploy [`56c8fcf`](https://github.com/syncweek-react-aad/react-aad/commit/56c8fcfa294a3782c54665e2d3db104f20d379c8) 181 | - Update .npmignore [`7219d12`](https://github.com/syncweek-react-aad/react-aad/commit/7219d1251c672a7de017bd05295dc57e59c194b9) 182 | - Update README.md [`8138405`](https://github.com/syncweek-react-aad/react-aad/commit/81384050766a6379150dbc7adc6f5da35b05ecdd) 183 | - have a development build option that doesn't use tslint [`66f9516`](https://github.com/syncweek-react-aad/react-aad/commit/66f9516bdc817cd75a7e90a52795f90f3e284453) 184 | - Adding source of script [`8383aeb`](https://github.com/syncweek-react-aad/react-aad/commit/8383aeb5164cf85ce325236105f237844f4799a5) 185 | - Adding sh before script path [`9d4f915`](https://github.com/syncweek-react-aad/react-aad/commit/9d4f915aa95b48125e25ed7dcca1a554f7e8b410) 186 | - Adding AAD_LOGOUT_SUCCESS case to reduxStore.js [`af9f0bd`](https://github.com/syncweek-react-aad/react-aad/commit/af9f0bd1a889d37dd071f5dacf42e75d84bac282) 187 | - Setting tsconfig to exclude sample folder [`ee9599c`](https://github.com/syncweek-react-aad/react-aad/commit/ee9599c497c506d15a1868b87a197bae6fbad0d3) 188 | -------------------------------------------------------------------------------- /packages/react-aad-msal/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /packages/react-aad-msal/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], 3 | plugins: ['@babel/proposal-class-properties', '@babel/plugin-syntax-dynamic-import'], 4 | comments: false, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/react-aad-msal/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], 3 | setupFiles: ['core-js/stable', 'regenerator-runtime/runtime', 'jest-localstorage-mock'], 4 | testMatch: ['/src/**/*(*.)@(test).[tj]s?(x)'], 5 | testEnvironment: 'node', 6 | testURL: 'http://localhost', 7 | transform: { 8 | '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest', 9 | }, 10 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$'], 11 | modulePathIgnorePatterns: ['\\.git'], 12 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 13 | }; 14 | -------------------------------------------------------------------------------- /packages/react-aad-msal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-aad-msal", 3 | "version": "2.3.5", 4 | "description": "A react component that integrates with Azure AD (v2, MSAL).", 5 | "private": false, 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/syncweek-react-aad/react-aad.git" 10 | }, 11 | "main": "./dist/commonjs/index.js", 12 | "types": "./dist/typings/index.d.ts", 13 | "author": "Laura Bochenek", 14 | "contributors": [ 15 | "Laura Bochenek ", 16 | "Omeed Musavi ", 17 | "Lilian Kasem ", 18 | "Tess DiStefano ", 19 | "Lucas Huet-Hudson ", 20 | "Zach Miller ", 21 | "P.J. Little ", 22 | "Shawn Cicoria ", 23 | "Andrew Craswell ", 24 | "Björn Dalfors " 25 | ], 26 | "files": [ 27 | "dist" 28 | ], 29 | "scripts": { 30 | "precommit": "lint-staged", 31 | "clean": "rimraf dist", 32 | "lint": "npm run lint:ts && npm run lint:types", 33 | "lint:ts": "eslint src/ --ext .ts,.tsx --color --fix", 34 | "lint:types": "tsc --project ./tsconfig.json --noEmit --pretty", 35 | "start": "npm run clean && npm run build:commonjs && cd ../../samples/react-javascript && npm start", 36 | "build": "npm run clean && npm run lint && npm run build:types && npm run build:commonjs && npm run build:esm && npm run build:umd", 37 | "build:commonjs": "babel src --out-dir dist/commonjs --extensions .ts,.tsx --source-maps --plugins @babel/plugin-transform-modules-commonjs", 38 | "build:umd": "webpack", 39 | "build:esm": "tsc -m es6 --outDir dist/es6 --declaration false", 40 | "build:types": "tsc --emitDeclarationOnly --outDir ./dist/typings/", 41 | "build:watch": "npm run build:commonjs -- --watch", 42 | "test": "jest --env=jsdom", 43 | "version": "auto-changelog -p && git add CHANGELOG.md" 44 | }, 45 | "dependencies": { 46 | "redux": "4.0.4" 47 | }, 48 | "devDependencies": { 49 | "@babel/cli": "7.7.7", 50 | "@babel/core": "7.7.7", 51 | "@babel/plugin-proposal-class-properties": "7.7.4", 52 | "@babel/plugin-syntax-dynamic-import": "7.7.4", 53 | "@babel/plugin-transform-modules-commonjs": "7.7.5", 54 | "@babel/preset-env": "7.7.7", 55 | "@babel/preset-react": "7.7.4", 56 | "@babel/preset-typescript": "7.7.7", 57 | "@types/jest": "24.0.18", 58 | "@types/react": "16.9.17", 59 | "@types/react-dom": "16.9.4", 60 | "@typescript-eslint/eslint-plugin": "2.13.0", 61 | "@typescript-eslint/parser": "2.13.0", 62 | "auto-changelog": "1.16.2", 63 | "babel-jest": "24.9.0", 64 | "babel-loader": "8.0.6", 65 | "clean-webpack-plugin": "3.0.0", 66 | "core-js": "3.6.1", 67 | "enzyme": "3.10.0", 68 | "enzyme-adapter-react-16": "1.14.0", 69 | "eslint": "6.8.0", 70 | "eslint-config-prettier": "6.8.0", 71 | "eslint-plugin-jsdoc": "18.6.0", 72 | "eslint-plugin-jsx-a11y": "6.2.3", 73 | "eslint-plugin-react": "7.17.0", 74 | "eslint-plugin-react-hooks": "2.3.0", 75 | "fork-ts-checker-webpack-plugin": "0.5.1", 76 | "husky": "3.0.4", 77 | "jest": "24.9.0", 78 | "jest-localstorage-mock": "2.4.0", 79 | "lint-staged": "9.2.3", 80 | "msal": "1.2.1", 81 | "prettier": "1.18.2", 82 | "react": "16.12.0", 83 | "react-dom": "16.12.0", 84 | "regenerator-runtime": "0.13.3", 85 | "rimraf": "3.0.0", 86 | "terser-webpack-plugin": "2.2.2", 87 | "typescript": "3.2.1", 88 | "webpack": "4.41.2", 89 | "webpack-bundle-analyzer": "3.6.0", 90 | "webpack-cli": "3.2.3", 91 | "webpack-stylish": "0.1.8" 92 | }, 93 | "peerDependencies": { 94 | "msal": ">=1.2.1", 95 | "react": ">=16.8.0" 96 | }, 97 | "engines": { 98 | "node": ">= 8.10.0", 99 | "npm": ">= 5.6.0" 100 | }, 101 | "keywords": [ 102 | "Azure AD", 103 | "MSAL", 104 | "React", 105 | "OAuth", 106 | "OAuth2", 107 | "AAD", 108 | "Microsoft Authentication Library", 109 | "Azure Active Directory", 110 | "Azure" 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/AccessTokenResponse.ts: -------------------------------------------------------------------------------- 1 | import { AuthResponse } from 'msal'; 2 | 3 | import { TokenType } from './enums'; 4 | 5 | export class AccessTokenResponse { 6 | public accessToken = ''; 7 | public scopes: string[] = []; 8 | public expiresOn: Date; 9 | public state = ''; 10 | 11 | constructor(response: AuthResponse) { 12 | if (response.tokenType !== TokenType.AccessToken) { 13 | throw new Error( 14 | `Can't construct an AccessTokenResponse from a AuthResponse that has a token type of "${response.tokenType}".`, 15 | ); 16 | } 17 | 18 | this.accessToken = response.accessToken; 19 | this.expiresOn = response.expiresOn; 20 | this.scopes = response.scopes; 21 | this.state = response.accountState; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/AuthenticationActionCreators.ts: -------------------------------------------------------------------------------- 1 | import { AuthError } from 'msal'; 2 | import { AnyAction } from 'redux'; 3 | import { AccessTokenResponse } from './AccessTokenResponse'; 4 | import { IdTokenResponse } from './IdTokenResponse'; 5 | import { IAccountInfo } from './interfaces'; 6 | import { AuthenticationActions, AuthenticationState } from './enums'; 7 | 8 | export abstract class AuthenticationActionCreators { 9 | public static initializing = (): AnyAction => ({ 10 | type: AuthenticationActions.Initializing, 11 | }); 12 | 13 | public static initialized = (): AnyAction => ({ 14 | type: AuthenticationActions.Initialized, 15 | }); 16 | 17 | public static loginSuccessful = (data: IAccountInfo): AnyAction => ({ 18 | payload: data, 19 | type: AuthenticationActions.LoginSuccess, 20 | }); 21 | 22 | public static loginFailed = (): AnyAction => ({ 23 | type: AuthenticationActions.LoginFailed, 24 | }); 25 | 26 | public static loginError = (error: AuthError): AnyAction => ({ 27 | payload: error, 28 | type: AuthenticationActions.LoginError, 29 | }); 30 | 31 | public static clearError = (): AnyAction => ({ 32 | type: AuthenticationActions.ClearError, 33 | }); 34 | 35 | public static logoutSuccessful = (): AnyAction => ({ 36 | type: AuthenticationActions.LogoutSuccess, 37 | }); 38 | 39 | public static acquireIdTokenSuccess = (token: IdTokenResponse): AnyAction => ({ 40 | payload: token, 41 | type: AuthenticationActions.AcquiredIdTokenSuccess, 42 | }); 43 | 44 | public static acquireIdTokenError = (error: AuthError): AnyAction => ({ 45 | payload: error, 46 | type: AuthenticationActions.AcquiredIdTokenError, 47 | }); 48 | 49 | public static acquireAccessTokenSuccess = (token: AccessTokenResponse): AnyAction => ({ 50 | payload: token, 51 | type: AuthenticationActions.AcquiredAccessTokenSuccess, 52 | }); 53 | 54 | public static acquireAccessTokenError = (error: AuthError): AnyAction => ({ 55 | payload: error, 56 | type: AuthenticationActions.AcquiredAccessTokenError, 57 | }); 58 | 59 | public static authenticatedStateChanged = (state: AuthenticationState): AnyAction => ({ 60 | payload: state, 61 | type: AuthenticationActions.AuthenticatedStateChanged, 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/IdTokenResponse.ts: -------------------------------------------------------------------------------- 1 | import { AuthResponse } from 'msal'; 2 | import { IdToken } from 'msal/lib-commonjs/IdToken'; 3 | 4 | import { TokenType } from './enums'; 5 | 6 | export class IdTokenResponse { 7 | public idToken: IdToken; 8 | public state = ''; 9 | 10 | constructor(response: AuthResponse) { 11 | if (response.tokenType !== TokenType.IdToken) { 12 | throw new Error( 13 | `Can't construct an IdTokenResponse from a AuthResponse that has a token type of "${response.tokenType}".`, 14 | ); 15 | } 16 | 17 | this.idToken = response.idToken; 18 | this.state = response.accountState; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/Logger.ts: -------------------------------------------------------------------------------- 1 | export class Logger { 2 | public static VERBOSE(message: string, ...optionalParams: any[]) { 3 | // eslint-disable-next-line no-console 4 | console.log(...['[VERBOSE] ' + message].concat(optionalParams)); 5 | } 6 | 7 | public static INFO(message: string, ...optionalParams: any[]) { 8 | // eslint-disable-next-line no-console 9 | console.info(...['[INFO] ' + message].concat(optionalParams)); 10 | } 11 | 12 | public static WARN(message: string, ...optionalParams: any[]) { 13 | // eslint-disable-next-line no-console 14 | console.warn(...['[WARN] ' + message].concat(optionalParams)); 15 | } 16 | 17 | public static ERROR(message: string, ...optionalParams: any[]) { 18 | // eslint-disable-next-line no-console 19 | console.error(...['[ERROR] ' + message].concat(optionalParams)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/MsalAuthProvider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthenticationParameters, 3 | AuthError, 4 | AuthResponse, 5 | ClientAuthError, 6 | Configuration, 7 | InteractionRequiredAuthError, 8 | UserAgentApplication, 9 | } from 'msal'; 10 | import { AnyAction, Store } from 'redux'; 11 | 12 | import { AccessTokenResponse } from './AccessTokenResponse'; 13 | import { AuthenticationActionCreators } from './AuthenticationActionCreators'; 14 | import { IdTokenResponse } from './IdTokenResponse'; 15 | import { IAccountInfo, IAuthProvider, IMsalAuthProviderConfig } from './interfaces'; 16 | import { Logger } from './Logger'; 17 | import { AuthenticationState, LoginType, TokenType } from './enums'; 18 | 19 | type AuthenticationStateHandler = (state: AuthenticationState) => void; 20 | type ErrorHandler = (error: AuthError | null) => void; 21 | type AccountInfoHandlers = (accountInfo: IAccountInfo | null) => void; 22 | 23 | export class MsalAuthProvider extends UserAgentApplication implements IAuthProvider { 24 | public authenticationState: AuthenticationState; 25 | 26 | /** 27 | * Gives access to the MSAL functionality for advanced usage. 28 | * 29 | * @deprecated The MsalAuthProvider class itself extends from UserAgentApplication and has the same functionality 30 | */ 31 | public UserAgentApplication: UserAgentApplication; 32 | 33 | protected _reduxStore: Store; 34 | protected _parameters: AuthenticationParameters; 35 | protected _options: IMsalAuthProviderConfig; 36 | protected _accountInfo: IAccountInfo | null; 37 | protected _error: AuthError | null; 38 | 39 | private _onAuthenticationStateHandlers = new Set(); 40 | private _onAccountInfoHandlers = new Set(); 41 | private _onErrorHandlers = new Set(); 42 | private _actionQueue: AnyAction[] = []; 43 | 44 | constructor( 45 | config: Configuration, 46 | parameters: AuthenticationParameters, 47 | options: IMsalAuthProviderConfig = { 48 | loginType: LoginType.Popup, 49 | tokenRefreshUri: window.location.origin, 50 | }, 51 | ) { 52 | super(config); 53 | 54 | // Required only for backward compatibility 55 | this.UserAgentApplication = this as UserAgentApplication; 56 | 57 | this.setAuthenticationParameters(parameters); 58 | this.setProviderOptions(options); 59 | 60 | this.initializeProvider(); 61 | } 62 | 63 | public login = async (parameters?: AuthenticationParameters) => { 64 | const params = parameters || this.getAuthenticationParameters(); 65 | 66 | // Clear any active authentication errors unless the code is executing from within 67 | // the token renewal iframe 68 | const error = this.getError(); 69 | if (error && error.errorCode !== 'block_token_requests') { 70 | this.setError(null); 71 | } 72 | 73 | const providerOptions = this.getProviderOptions(); 74 | if (providerOptions.loginType === LoginType.Redirect) { 75 | this.setAuthenticationState(AuthenticationState.InProgress); 76 | try { 77 | this.loginRedirect(params); 78 | } catch (error) { 79 | Logger.ERROR(error); 80 | 81 | this.setError(error); 82 | this.setAuthenticationState(AuthenticationState.Unauthenticated); 83 | } 84 | } else if (providerOptions.loginType === LoginType.Popup) { 85 | try { 86 | this.setAuthenticationState(AuthenticationState.InProgress); 87 | await this.loginPopup(params); 88 | } catch (error) { 89 | Logger.ERROR(error); 90 | 91 | this.setError(error); 92 | this.setAuthenticationState(AuthenticationState.Unauthenticated); 93 | } 94 | 95 | await this.processLogin(); 96 | } 97 | }; 98 | 99 | public logout = (): void => { 100 | super.logout(); 101 | 102 | this.dispatchAction(AuthenticationActionCreators.logoutSuccessful()); 103 | }; 104 | 105 | public getAccountInfo = (): IAccountInfo | null => { 106 | return this._accountInfo ? { ...this._accountInfo } : null; 107 | }; 108 | 109 | public getAccessToken = async (parameters?: AuthenticationParameters): Promise => { 110 | const providerOptions = this.getProviderOptions(); 111 | 112 | // The parameters to be used when silently refreshing the token 113 | const refreshParams = { 114 | ...(parameters || this.getAuthenticationParameters()), 115 | // Use the redirectUri that was passed, otherwise use the configured tokenRefreshUri 116 | redirectUri: (parameters && parameters.redirectUri) || providerOptions.tokenRefreshUri, 117 | }; 118 | 119 | /* In this library, acquireTokenSilent is being called only when there is an accountInfo of an expired session. 120 | * In a scenario where user interaction is required, username from the account info is passed as 'login_hint' 121 | * parameter which redirects user to user's organization login page. So 'domain_hint' is not required to be 122 | * passed for silent calls. Hence, the below code is to avoid sending domain_hint. This also solves the issue 123 | * of multiple domain_hint param being added by the MSAL.js. 124 | */ 125 | if (refreshParams.extraQueryParameters && refreshParams.extraQueryParameters.domain_hint) { 126 | delete refreshParams.extraQueryParameters.domain_hint; 127 | } 128 | 129 | try { 130 | const response = await this.acquireTokenSilent(refreshParams); 131 | 132 | this.handleAcquireTokenSuccess(response); 133 | this.setAuthenticationState(AuthenticationState.Authenticated); 134 | 135 | return new AccessTokenResponse(response); 136 | } catch (error) { 137 | // The parameters to be used if silent refresh failed, and a new login needs to be initiated 138 | const loginParams = { 139 | ...(parameters || this.getAuthenticationParameters()), 140 | }; 141 | 142 | this.dispatchAction(AuthenticationActionCreators.acquireAccessTokenError(error)); 143 | const response = await this.loginToRefreshToken(error, loginParams); 144 | 145 | return new AccessTokenResponse(response); 146 | } 147 | }; 148 | 149 | public getIdToken = async (parameters?: AuthenticationParameters): Promise => { 150 | const providerOptions = this.getProviderOptions(); 151 | const config = this.getCurrentConfiguration(); 152 | const clientId = config.auth.clientId; 153 | 154 | // The parameters to be used when silently refreshing the token 155 | const refreshParams = { 156 | ...(parameters || this.getAuthenticationParameters()), 157 | // Use the redirectUri that was passed, otherwise use the configured tokenRefreshUri 158 | redirectUri: (parameters && parameters.redirectUri) || providerOptions.tokenRefreshUri, 159 | // Pass the clientId as the only scope to get a renewed IdToken if it has expired 160 | scopes: [clientId], 161 | }; 162 | 163 | /* In this library, acquireTokenSilent is being called only when there is an accountInfo of an expired session. 164 | * In a scenario where user interaction is required, username from the account info is passed as 'login_hint' 165 | * parameter which redirects user to user's organization login page. So 'domain_hint' is not required to be 166 | * passed for silent calls. Hence, the below code is to avoid sending domain_hint. This also solves the issue 167 | * of multiple domain_hint param being added by the MSAL.js. 168 | */ 169 | if (refreshParams.extraQueryParameters && refreshParams.extraQueryParameters.domain_hint) { 170 | delete refreshParams.extraQueryParameters.domain_hint; 171 | } 172 | 173 | try { 174 | const response = await this.acquireTokenSilent(refreshParams); 175 | 176 | this.handleAcquireTokenSuccess(response); 177 | this.setAuthenticationState(AuthenticationState.Authenticated); 178 | 179 | return new IdTokenResponse(response); 180 | } catch (error) { 181 | // The parameters to be used if silent refresh failed, and a new login needs to be initiated 182 | const loginParams = { 183 | ...(parameters || this.getAuthenticationParameters()), 184 | }; 185 | 186 | // If the parameters do not specify a login hint and the user already has a session cached, 187 | // prefer the cached user name to bypass the account selection process if possible 188 | const account = this.getAccount(); 189 | if (account && (!parameters || !parameters.loginHint)) { 190 | loginParams.loginHint = account.userName; 191 | } 192 | 193 | this.dispatchAction(AuthenticationActionCreators.acquireIdTokenError(error)); 194 | const response = await this.loginToRefreshToken(error, loginParams); 195 | 196 | return new IdTokenResponse(response); 197 | } 198 | }; 199 | 200 | public getAuthenticationParameters = (): AuthenticationParameters => { 201 | return { ...this._parameters }; 202 | }; 203 | 204 | public getError = () => { 205 | return this._error ? { ...this._error } : null; 206 | }; 207 | 208 | public setAuthenticationParameters = (parameters: AuthenticationParameters): void => { 209 | this._parameters = { ...parameters }; 210 | }; 211 | 212 | public getProviderOptions = (): IMsalAuthProviderConfig => { 213 | return { ...this._options }; 214 | }; 215 | 216 | public setProviderOptions = (options: IMsalAuthProviderConfig) => { 217 | this._options = { ...options }; 218 | if (options.loginType === LoginType.Redirect) { 219 | this.handleRedirectCallback(this.authenticationRedirectCallback); 220 | } 221 | }; 222 | 223 | public registerReduxStore = (store: Store): void => { 224 | this._reduxStore = store; 225 | while (this._actionQueue.length) { 226 | const action = this._actionQueue.shift(); 227 | if (action) { 228 | this.dispatchAction(action); 229 | } 230 | } 231 | }; 232 | 233 | public registerAuthenticationStateHandler = (listener: AuthenticationStateHandler) => { 234 | this._onAuthenticationStateHandlers.add(listener); 235 | listener(this.authenticationState); 236 | }; 237 | 238 | public unregisterAuthenticationStateHandler = (listener: AuthenticationStateHandler) => { 239 | this._onAuthenticationStateHandlers.delete(listener); 240 | }; 241 | 242 | public registerAcountInfoHandler = (listener: AccountInfoHandlers) => { 243 | this._onAccountInfoHandlers.add(listener); 244 | listener(this._accountInfo); 245 | }; 246 | 247 | public unregisterAccountInfoHandler = (listener: AccountInfoHandlers) => { 248 | this._onAccountInfoHandlers.delete(listener); 249 | }; 250 | 251 | public registerErrorHandler = (listener: ErrorHandler) => { 252 | this._onErrorHandlers.add(listener); 253 | listener(this._error); 254 | }; 255 | 256 | public unregisterErrorHandler = (listener: ErrorHandler) => { 257 | this._onErrorHandlers.delete(listener); 258 | }; 259 | 260 | private setError = (error: AuthError | null) => { 261 | this._error = error ? { ...error } : null; 262 | 263 | if (error) { 264 | this.dispatchAction(AuthenticationActionCreators.loginError(error)); 265 | } 266 | 267 | this._onErrorHandlers.forEach(listener => listener(this._error)); 268 | 269 | return { ...this._error }; 270 | }; 271 | 272 | private loginToRefreshToken = async ( 273 | error: AuthError, 274 | parameters?: AuthenticationParameters, 275 | ): Promise => { 276 | const providerOptions = this.getProviderOptions(); 277 | const params = parameters || this.getAuthenticationParameters(); 278 | 279 | if (error instanceof InteractionRequiredAuthError) { 280 | if (providerOptions.loginType === LoginType.Redirect) { 281 | this.acquireTokenRedirect(params); 282 | 283 | // Nothing to return, the user is redirected to the login page 284 | return new Promise(resolve => resolve()); 285 | } 286 | 287 | try { 288 | const response = await this.acquireTokenPopup(params); 289 | this.handleAcquireTokenSuccess(response); 290 | this.setAuthenticationState(AuthenticationState.Authenticated); 291 | return response; 292 | } catch (error) { 293 | Logger.ERROR(error); 294 | 295 | this.setError(error); 296 | this.setAuthenticationState(AuthenticationState.Unauthenticated); 297 | 298 | throw error; 299 | } 300 | } else { 301 | Logger.ERROR(error as any); 302 | 303 | this.setError(error); 304 | this.setAuthenticationState(AuthenticationState.Unauthenticated); 305 | 306 | throw error; 307 | } 308 | }; 309 | 310 | private authenticationRedirectCallback = (error: AuthError) => { 311 | if (error) { 312 | this.setError(error); 313 | } 314 | this.processLogin(); 315 | }; 316 | 317 | private initializeProvider = async () => { 318 | this.dispatchAction(AuthenticationActionCreators.initializing()); 319 | 320 | await this.processLogin(); 321 | 322 | this.dispatchAction(AuthenticationActionCreators.initialized()); 323 | }; 324 | 325 | private processLogin = async () => { 326 | if (this.getError()) { 327 | this.handleLoginFailed(); 328 | 329 | this.setAuthenticationState(AuthenticationState.Unauthenticated); 330 | } else if (this.getAccount()) { 331 | try { 332 | // If the IdToken has expired, refresh it. Otherwise use the cached token 333 | await this.getIdToken(); 334 | 335 | this.handleLoginSuccess(); 336 | } catch (error) { 337 | // Swallow the error if the user isn't authenticated, just set to Unauthenticated 338 | if (!(error instanceof ClientAuthError && error.errorCode === 'user_login_error')) { 339 | Logger.ERROR(error); 340 | this.setError(error); 341 | } 342 | 343 | this.setAuthenticationState(AuthenticationState.Unauthenticated); 344 | } 345 | } else if (this.getLoginInProgress()) { 346 | this.setAuthenticationState(AuthenticationState.InProgress); 347 | } else { 348 | this.setAuthenticationState(AuthenticationState.Unauthenticated); 349 | } 350 | }; 351 | 352 | private setAuthenticationState = (state: AuthenticationState): AuthenticationState => { 353 | if (this.authenticationState !== state) { 354 | this.authenticationState = state; 355 | 356 | this.dispatchAction(AuthenticationActionCreators.authenticatedStateChanged(state)); 357 | this._onAuthenticationStateHandlers.forEach(listener => listener(state)); 358 | } 359 | 360 | return this.authenticationState; 361 | }; 362 | 363 | private setAccountInfo = (response: AuthResponse): IAccountInfo => { 364 | const accountInfo: IAccountInfo = this.getAccountInfo() || ({ account: response.account } as IAccountInfo); 365 | 366 | // Depending on the token type of the auth response, update the correct property 367 | if (response.tokenType === TokenType.IdToken) { 368 | accountInfo.jwtIdToken = response.idToken.rawIdToken; 369 | } else if (response.tokenType === TokenType.AccessToken) { 370 | accountInfo.jwtAccessToken = response.accessToken; 371 | } 372 | 373 | this._accountInfo = { ...accountInfo }; 374 | this._onAccountInfoHandlers.forEach(listener => listener(this._accountInfo)); 375 | 376 | return { ...this._accountInfo }; 377 | }; 378 | 379 | private dispatchAction = (action: AnyAction): void => { 380 | if (this._reduxStore) { 381 | this._reduxStore.dispatch(action); 382 | } else { 383 | this._actionQueue.push(action); 384 | } 385 | }; 386 | 387 | private handleAcquireTokenSuccess = (response: AuthResponse): void => { 388 | this.setAccountInfo(response); 389 | 390 | if (response.tokenType === TokenType.IdToken) { 391 | const token = new IdTokenResponse(response); 392 | this.dispatchAction(AuthenticationActionCreators.acquireIdTokenSuccess(token)); 393 | } else if (response.tokenType === TokenType.AccessToken) { 394 | const token = new AccessTokenResponse(response); 395 | this.dispatchAction(AuthenticationActionCreators.acquireAccessTokenSuccess(token)); 396 | } 397 | }; 398 | 399 | private handleLoginFailed = (): void => { 400 | const error = this.getError(); 401 | if (error) { 402 | this.dispatchAction(AuthenticationActionCreators.loginFailed()); 403 | } 404 | }; 405 | 406 | private handleLoginSuccess = (): void => { 407 | const account = this.getAccountInfo(); 408 | if (account) { 409 | this.dispatchAction(AuthenticationActionCreators.loginSuccessful(account)); 410 | } 411 | }; 412 | } 413 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/components/AzureAD.tsx: -------------------------------------------------------------------------------- 1 | import { default as React, useCallback, useEffect, useMemo, useState } from 'react'; 2 | import { Store } from 'redux'; 3 | 4 | import { AuthError } from 'msal'; 5 | import { MsalAuthProvider } from '..'; 6 | import { IAccountInfo } from '../interfaces'; 7 | import { AuthenticationState } from '../enums'; 8 | 9 | type AccountInfoCallback = (token: IAccountInfo) => void; 10 | type UnauthenticatedFunction = (login: LoginFunction) => JSX.Element; 11 | type AuthenticatedFunction = (logout: LogoutFunction) => JSX.Element; 12 | type LoginFunction = () => void; 13 | type LogoutFunction = () => void; 14 | 15 | export interface IAzureADFunctionProps { 16 | login: LoginFunction; 17 | logout: LogoutFunction; 18 | authenticationState: AuthenticationState; 19 | accountInfo: IAccountInfo | null; 20 | error: AuthError | null; 21 | } 22 | 23 | export interface IAzureADProps { 24 | provider: MsalAuthProvider; 25 | unauthenticatedFunction?: UnauthenticatedFunction; 26 | authenticatedFunction?: AuthenticatedFunction; 27 | accountInfoCallback?: AccountInfoCallback; 28 | reduxStore?: Store; 29 | forceLogin?: boolean; 30 | } 31 | 32 | export const AzureAD: React.FunctionComponent = props => { 33 | const { authenticatedFunction, unauthenticatedFunction, provider, forceLogin, accountInfoCallback } = props; 34 | const [accountInfo, _setAccountInfo] = useState(provider.getAccountInfo()); 35 | const [authenticationState, _setAuthenticationState] = useState(provider.authenticationState); 36 | const [error, _setError] = useState(provider.getError()); 37 | 38 | // On component mounted 39 | useEffect(() => { 40 | provider.registerAuthenticationStateHandler(setAuthenticationState); 41 | provider.registerAcountInfoHandler(onAccountInfoChanged); 42 | provider.registerErrorHandler(setError); 43 | 44 | if (props.reduxStore) { 45 | provider.registerReduxStore(props.reduxStore); 46 | } 47 | 48 | if (forceLogin && authenticationState === AuthenticationState.Unauthenticated && !error) { 49 | login(); 50 | } 51 | 52 | // Cleanup on unmount 53 | return () => { 54 | provider.unregisterAuthenticationStateHandler(setAuthenticationState); 55 | provider.unregisterAccountInfoHandler(onAccountInfoChanged); 56 | provider.unregisterErrorHandler(setError); 57 | }; 58 | }, [authenticationState, accountInfo, error]); 59 | 60 | const login = useCallback(() => { 61 | provider.login(); 62 | }, [provider]); 63 | 64 | const logout = useCallback(() => { 65 | if (authenticationState !== AuthenticationState.Authenticated) { 66 | return; 67 | } 68 | provider.logout(); 69 | }, [authenticationState, provider]); 70 | 71 | const setAuthenticationState = useCallback( 72 | (newState: AuthenticationState) => { 73 | if (newState !== authenticationState) { 74 | _setAuthenticationState(newState); 75 | 76 | if (newState === AuthenticationState.Unauthenticated && forceLogin && !error) { 77 | login(); 78 | } 79 | } 80 | }, 81 | [authenticationState, forceLogin, error], 82 | ); 83 | 84 | const setError = useCallback( 85 | (newError: AuthError) => { 86 | if (newError !== error) { 87 | _setError(newError); 88 | } 89 | }, 90 | [error], 91 | ); 92 | 93 | const onAccountInfoChanged = useCallback( 94 | (newAccountInfo: IAccountInfo) => { 95 | _setAccountInfo(newAccountInfo); 96 | 97 | if (accountInfoCallback) { 98 | // eslint-disable-next-line no-console 99 | console.warn( 100 | 'Warning! The accountInfoCallback callback has been deprecated and will be removed in a future release.', 101 | ); 102 | accountInfoCallback(newAccountInfo); 103 | } 104 | }, 105 | [accountInfoCallback], 106 | ); 107 | 108 | // The authentication data to be passed to the children() if it's a function 109 | const childrenFunctionProps = useMemo( 110 | () => ({ 111 | accountInfo, 112 | authenticationState, 113 | error, 114 | login, 115 | logout, 116 | }), 117 | [accountInfo, authenticationState, error, login, logout], 118 | ); 119 | 120 | /** 121 | * @param children 122 | * @param childrenProps 123 | */ 124 | function getChildrenOrFunction(children: any, childrenProps: IAzureADFunctionProps) { 125 | if (children) { 126 | // tslint:disable-next-line: triple-equals 127 | if (isChildrenFunction(children)) { 128 | return (children as (props: IAzureADFunctionProps) => {})(childrenProps); 129 | } else { 130 | return children; 131 | } 132 | } else { 133 | return null; 134 | } 135 | } 136 | 137 | /** 138 | * @param children 139 | */ 140 | function isChildrenFunction(children: any) { 141 | return typeof children == 'function' || false; 142 | } 143 | 144 | // Render logic 145 | switch (authenticationState) { 146 | case AuthenticationState.Authenticated: 147 | if (authenticatedFunction) { 148 | const authFunctionResult = authenticatedFunction(logout); 149 | 150 | // eslint-disable-next-line no-console 151 | console.warn( 152 | 'Warning! The authenticatedFunction callback has been deprecated and will be removed in a future release.', 153 | ); 154 | 155 | if (authFunctionResult) { 156 | return authFunctionResult; 157 | } 158 | } 159 | 160 | // If there is no authenticatedFunction, or it returned null, render the children 161 | return getChildrenOrFunction(props.children, childrenFunctionProps); 162 | case AuthenticationState.Unauthenticated: 163 | if (unauthenticatedFunction) { 164 | // eslint-disable-next-line no-console 165 | console.warn( 166 | 'Warning! The unauthenticatedFunction callback has been deprecated and will be removed in a future release.', 167 | ); 168 | return unauthenticatedFunction(login) || null; 169 | } 170 | 171 | // If state is Uauthenticated or InProgress, only return the children if it's a function 172 | // If the children prop is a function, we will pass state changes to be handled by the consumer 173 | // eslint-disable-next-line no-fallthrough 174 | case AuthenticationState.InProgress: 175 | if (isChildrenFunction(props.children)) { 176 | return getChildrenOrFunction(props.children, childrenFunctionProps); 177 | } 178 | return null; 179 | default: 180 | return null; 181 | } 182 | }; 183 | 184 | AzureAD.displayName = 'AzureAD'; 185 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/components/withAuthentication.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { AzureAD, IAzureADProps } from './AzureAD'; 4 | 5 | export const withAuthentication =

( 6 | WrappedComponent: React.ComponentType

, 7 | parameters: IAzureADProps, 8 | ): React.FunctionComponent

=> { 9 | // tslint:disable-next-line: no-shadowed-variable 10 | const withAuthentication: React.FunctionComponent = (props: any) => { 11 | const propParams: IAzureADProps = { forceLogin: true, ...parameters }; 12 | 13 | withAuthentication.displayName = `withAuthentication(${WrappedComponent.displayName || WrappedComponent.name}`; 14 | return ( 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | return withAuthentication; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/enums/AuthenticationActions.ts: -------------------------------------------------------------------------------- 1 | export enum AuthenticationActions { 2 | Initializing = 'AAD_INITIALIZING', 3 | Initialized = 'AAD_INITIALIZED', 4 | LoginSuccess = 'AAD_LOGIN_SUCCESS', 5 | LoginFailed = 'AAD_LOGIN_FAILED', 6 | LoginError = 'AAD_LOGIN_ERROR', 7 | ClearError = 'AAD_CLEAR_ERROR', 8 | LogoutSuccess = 'AAD_LOGOUT_SUCCESS', 9 | AcquiredIdTokenSuccess = 'AAD_ACQUIRED_ID_TOKEN_SUCCESS', 10 | AcquiredIdTokenError = 'AAD_ACQUIRED_ID_TOKEN_ERROR', 11 | AcquiredAccessTokenSuccess = 'AAD_ACQUIRED_ACCESS_TOKEN_SUCCESS', 12 | AcquiredAccessTokenError = 'AAD_ACQUIRED_ACCESS_TOKEN_ERROR', 13 | AuthenticatedStateChanged = 'AAD_AUTHENTICATED_STATE_CHANGED', 14 | } 15 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/enums/AuthenticationState.ts: -------------------------------------------------------------------------------- 1 | export enum AuthenticationState { 2 | Unauthenticated = 'Unauthenticated', 3 | InProgress = 'InProgress', 4 | Authenticated = 'Authenticated', 5 | } 6 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/enums/LoginType.ts: -------------------------------------------------------------------------------- 1 | export enum LoginType { 2 | Popup, 3 | Redirect, 4 | } 5 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/enums/TokenType.ts: -------------------------------------------------------------------------------- 1 | export enum TokenType { 2 | IdToken = 'id_token', 3 | AccessToken = 'access_token', 4 | } 5 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/enums/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AuthenticationActions'; 2 | export * from './AuthenticationState'; 3 | export * from './LoginType'; 4 | export * from './TokenType'; 5 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/index.test.tsx: -------------------------------------------------------------------------------- 1 | import * as Msal from 'msal'; 2 | import * as React from 'react'; 3 | import * as ReactDOM from 'react-dom'; 4 | 5 | require('jest-localstorage-mock'); // tslint:disable-line 6 | 7 | import { AzureAD, LoginType } from './index'; 8 | import { MsalAuthProvider } from './MsalAuthProvider'; 9 | 10 | let Enzyme; 11 | let Adapter; 12 | let authProvider: MsalAuthProvider; 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | let testAccount: Msal.Account; 15 | 16 | beforeAll(() => { 17 | Enzyme = require('enzyme'); 18 | Adapter = require('enzyme-adapter-react-16'); 19 | 20 | Enzyme.configure({ adapter: new Adapter() }); 21 | }); 22 | 23 | beforeEach(() => { 24 | // values stored in tests will also be available in other tests unless you run 25 | localStorage.clear(); 26 | 27 | authProvider = new MsalAuthProvider( 28 | { 29 | auth: { 30 | authority: 'https://login.microsoftonline.com/common', 31 | clientId: '', 32 | }, 33 | cache: { 34 | cacheLocation: 'sessionStorage' as Msal.CacheLocation, 35 | }, 36 | }, 37 | { 38 | scopes: ['openid'], 39 | }, 40 | { 41 | loginType: LoginType.Popup, 42 | }, 43 | ); 44 | 45 | testAccount = { 46 | accountIdentifier: 'Something', 47 | environment: 'testEnv', 48 | homeAccountIdentifier: 'testIdentifier', 49 | idToken: {}, 50 | idTokenClaims: {}, 51 | name: 'Lilian', 52 | sid: 'sid', 53 | userName: 'LilUsername', 54 | }; 55 | }); 56 | 57 | it('renders without crashing', () => { 58 | const unauthenticatedFunction = () => { 59 | return ( 60 |

61 |

unauthenticatedFunction

62 |
63 | ); 64 | }; 65 | 66 | const authenticatedFunction = () => { 67 | return ( 68 |
69 |

authenticatedFunction

70 |
71 | ); 72 | }; 73 | 74 | const accountInfoCallback = () => { 75 | // empty 76 | }; 77 | 78 | const div = document.createElement('div'); 79 | ReactDOM.render( 80 | , 86 | div, 87 | ); 88 | ReactDOM.unmountComponentAtNode(div); 89 | }); 90 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/index.ts: -------------------------------------------------------------------------------- 1 | export { IAccountInfo, IMsalAuthProviderConfig } from './interfaces'; 2 | export { AuthenticationActions, AuthenticationState, LoginType } from './enums'; 3 | 4 | export { AccessTokenResponse } from './AccessTokenResponse'; 5 | export { IdTokenResponse } from './IdTokenResponse'; 6 | export { MsalAuthProvider } from './MsalAuthProvider'; 7 | export { AuthenticationActionCreators } from './AuthenticationActionCreators'; 8 | 9 | import { AzureAD } from './components/AzureAD'; 10 | export * from './components/AzureAD'; 11 | export { withAuthentication } from './components/withAuthentication'; 12 | 13 | export default AzureAD; 14 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/interfaces/IAccountInfo.ts: -------------------------------------------------------------------------------- 1 | import { Account } from 'msal'; 2 | 3 | export interface IAccountInfo { 4 | jwtAccessToken: string; 5 | jwtIdToken: string; 6 | account: Account; 7 | } 8 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/interfaces/IAuthProvider.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationState } from '../enums'; 2 | import { IAccountInfo } from './'; 3 | 4 | export interface IAuthProvider { 5 | onAuthenticationStateChanged?: (state: AuthenticationState, account?: IAccountInfo) => void; 6 | authenticationState: AuthenticationState; 7 | 8 | getAccountInfo(): IAccountInfo | null; 9 | login(): void; 10 | logout(): void; 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/interfaces/IMsalAuthProviderConfig.ts: -------------------------------------------------------------------------------- 1 | import { LoginType } from '../enums'; 2 | 3 | export interface IMsalAuthProviderConfig { 4 | // Determines whether the login process is executed in a new popup window, 5 | // or by redirecting to the login page 6 | loginType: LoginType; 7 | // When a token is refreshed it will be done by loading a page in an iframe. 8 | // Rather than reloading the same page, we can point to an empty html file which will prevent 9 | // site resources from being loaded twice. 10 | tokenRefreshUri?: string; 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-aad-msal/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './IAccountInfo'; 2 | export * from './IAuthProvider'; 3 | export * from './IMsalAuthProviderConfig'; 4 | -------------------------------------------------------------------------------- /packages/react-aad-msal/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist/commonjs", 4 | "module": "commonjs", 5 | "target": "es5", 6 | "lib": ["es6", "dom"], 7 | "sourceMap": true, 8 | "allowJs": false, 9 | "declaration": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": false, 20 | "baseUrl": ".", 21 | "esModuleInterop": true 22 | }, 23 | "include": ["**/*"], 24 | "exclude": ["node_modules", "dist"], 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-aad-msal/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | const package = require('./package'); 4 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 5 | const TerserPlugin = require('terser-webpack-plugin'); 6 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 7 | const Stylish = require('webpack-stylish'); 8 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 9 | 10 | const moduleName = package.name; 11 | const outputFolder = path.resolve(__dirname, 'dist/umd'); 12 | const srcFolder = path.resolve(__dirname, 'src'); 13 | const PATHS = { 14 | srcFolder, 15 | entryPoint: path.join(srcFolder, 'index.ts'), 16 | outputFolder, 17 | bundleReport: path.join(outputFolder, 'bundle_report.html'), 18 | bundleStats: path.join(outputFolder, 'bundle_stats.html'), 19 | }; 20 | 21 | module.exports = { 22 | mode: 'production', 23 | entry: { 24 | [moduleName]: [PATHS.entryPoint], 25 | [moduleName + '.min']: [PATHS.entryPoint], 26 | }, 27 | output: { 28 | path: PATHS.outputFolder, 29 | filename: '[name].js', 30 | library: moduleName, 31 | libraryTarget: 'umd', 32 | umdNamedDefine: true, 33 | }, 34 | externals: ['react', 'msal'], 35 | resolve: { 36 | extensions: ['.ts', '.tsx', '.js'], 37 | }, 38 | devtool: 'source-map', 39 | plugins: [ 40 | new CleanWebpackPlugin(), 41 | new ForkTsCheckerWebpackPlugin(), 42 | new BundleAnalyzerPlugin({ 43 | analyzerMode: 'static', 44 | openAnalyzer: false, 45 | reportFilename: PATHS.bundleReport, 46 | generateStatsFile: false, 47 | statsFilename: PATHS.bundleStats, 48 | defaultSizes: 'gzip', 49 | }), 50 | new Stylish(), 51 | ], 52 | optimization: { 53 | minimizer: [ 54 | new TerserPlugin({ 55 | include: /\.min\.js$/, 56 | terserOptions: { 57 | parse: { 58 | // we want terser to parse ecma 8 code. However, we don't want it 59 | // to apply any minfication steps that turns valid ecma 5 code 60 | // into invalid ecma 5 code. This is why the 'compress' and 'output' 61 | // sections only apply transformations that are ecma 5 safe 62 | // https://github.com/facebook/create-react-app/pull/4234 63 | ecma: 8, 64 | }, 65 | compress: { 66 | ecma: 5, 67 | warnings: false, 68 | // Disabled because of an issue with Uglify breaking seemingly valid code: 69 | // https://github.com/facebook/create-react-app/issues/2376 70 | // Pending further investigation: 71 | // https://github.com/mishoo/UglifyJS2/issues/2011 72 | comparisons: false, 73 | // Disabled because of an issue with Terser breaking valid code: 74 | // https://github.com/facebook/create-react-app/issues/5250 75 | // Pending futher investigation: 76 | // https://github.com/terser-js/terser/issues/120 77 | inline: 2, 78 | }, 79 | mangle: { 80 | safari10: true, 81 | }, 82 | output: { 83 | ecma: 5, 84 | comments: false, 85 | // Turned on because emoji and regex is not minified properly using default 86 | // https://github.com/facebook/create-react-app/issues/2488 87 | // eslint-disable-next-line camelcase 88 | ascii_only: true, 89 | }, 90 | }, 91 | // Use multi-process parallel running to improve the build speed 92 | // Default number of concurrent runs: os.cpus().length - 1 93 | parallel: true, 94 | // Enable file caching 95 | cache: true, 96 | sourceMap: true, 97 | }), 98 | ], 99 | }, 100 | module: { 101 | rules: [ 102 | { 103 | test: /\.(js|jsx|ts|tsx)$/, 104 | include: PATHS.srcFolder, 105 | loader: 'babel-loader', 106 | options: { 107 | cacheDirectory: true, 108 | // Save disk space when time isn't as important 109 | cacheCompression: true, 110 | compact: true, 111 | }, 112 | }, 113 | ], 114 | }, 115 | }; 116 | -------------------------------------------------------------------------------- /samples/react-javascript/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /samples/react-javascript/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /samples/react-javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-javascript-sample", 3 | "version": "1.0.0", 4 | "description": "A sample React application that demonstrates the Azure AD react component.", 5 | "private": true, 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/syncweek-react-aad/react-aad.git" 10 | }, 11 | "author": "Laura Bochenek", 12 | "contributors": [ 13 | "Laura Bochenek ", 14 | "Omeed Musavi ", 15 | "Lilian Kasem ", 16 | "Tess DiStefano ", 17 | "Lucas Huet-Hudson ", 18 | "Zach Miller ", 19 | "P.J. Little ", 20 | "Shawn Cicoria ", 21 | "Andrew Craswell ", 22 | "Björn Dalfors " 23 | ], 24 | "dependencies": { 25 | "@testing-library/jest-dom": "^4.2.4", 26 | "@testing-library/react": "^9.3.2", 27 | "@testing-library/user-event": "^7.1.2", 28 | "msal": "^1.2.0", 29 | "react": "^16.12.0", 30 | "react-aad-msal": "^2.3.2", 31 | "react-dom": "^16.12.0", 32 | "react-redux": "^7.1.3", 33 | "react-scripts": "3.4.0", 34 | "redux": "^4.0.5" 35 | }, 36 | "devDependencies": { 37 | "rimraf": "^3.0.0" 38 | }, 39 | "scripts": { 40 | "clean": "rimraf build", 41 | "start": "react-scripts start", 42 | "build": "react-scripts build", 43 | "test": "react-scripts test --env=jsdom", 44 | "eject": "react-scripts eject" 45 | }, 46 | "eslintConfig": { 47 | "extends": "react-app" 48 | }, 49 | "browserslist": { 50 | "production": [ 51 | ">0.2%", 52 | "not dead", 53 | "not ie < 11", 54 | "not op_mini all" 55 | ], 56 | "development": [ 57 | "last 1 chrome version", 58 | "last 1 firefox version", 59 | "last 1 safari version" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /samples/react-javascript/public/auth.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /samples/react-javascript/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncweek-react-aad/react-aad/d12597f7fd3ba661cdc3f69d41301282124c353e/samples/react-javascript/public/favicon.ico -------------------------------------------------------------------------------- /samples/react-javascript/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /samples/react-javascript/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /samples/react-javascript/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-header { 6 | background-color: #222; 7 | height: 25px; 8 | padding: 20px; 9 | color: white; 10 | margin-bottom: 2em; 11 | } 12 | 13 | .App-title { 14 | font-size: 1.5em; 15 | margin: 0; 16 | } 17 | 18 | .App-intro { 19 | font-size: large; 20 | } 21 | 22 | .SampleContainer { 23 | width: 100%; 24 | display: flex; 25 | justify-content: center; 26 | } 27 | 28 | .SampleBox { 29 | border: 1px solid black; 30 | width: 20em; 31 | margin: 2em; 32 | padding: 10px; 33 | border-radius: 10px; 34 | } 35 | 36 | .SampleHeader { 37 | font-size: 1.25em; 38 | } 39 | 40 | .Button { 41 | background-color: #00a1f1; 42 | border: none; 43 | color: white; 44 | display: inline-block; 45 | padding: 15px; 46 | font-size: 15px; 47 | cursor: pointer; 48 | } 49 | 50 | .Button:disabled { 51 | background-color: grey; 52 | } 53 | -------------------------------------------------------------------------------- /samples/react-javascript/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { AzureAD, AuthenticationState } from 'react-aad-msal'; 3 | import { basicReduxStore } from './reduxStore'; 4 | 5 | // Import the authentication provider which holds the default settings 6 | import { authProvider } from './authProvider'; 7 | 8 | import SampleAppButtonLaunch from './SampleAppButtonLaunch'; 9 | import SampleAppRedirectOnLaunch from './SampleAppRedirectOnLaunch'; 10 | 11 | import './App.css'; 12 | 13 | class App extends Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | accountInfo: null, 19 | sampleType: null, 20 | }; 21 | 22 | const sampleType = localStorage.getItem('sampleType'); 23 | if (sampleType) { 24 | this.state.sampleType = sampleType; 25 | } 26 | } 27 | 28 | handleClick = sampleType => { 29 | this.setState({ sampleType }); 30 | localStorage.setItem('sampleType', sampleType); 31 | }; 32 | 33 | render() { 34 | let sampleBox; 35 | 36 | switch (this.state.sampleType) { 37 | case 'popup': 38 | sampleBox = ( 39 |
40 |

Button Login

41 |

This example will launch a popup dialog to allow for authentication with Azure Active Directory

42 | 43 |
44 | ); 45 | break; 46 | case 'redirect': 47 | sampleBox = ( 48 |
49 |

Automatic Redirect

50 |

51 | This example shows how you can use the AzureAD component to redirect the login screen automatically on 52 | page load. Click the checkbox below to enable the redirect and refresh your browser window. 53 |

54 | 55 |
56 | ); 57 | break; 58 | default: 59 | break; 60 | } 61 | 62 | return ( 63 |
64 |
65 |

Welcome to the react-aad-msal sample

66 |
67 | 68 | 69 | {({ accountInfo, authenticationState, error }) => { 70 | return ( 71 | 72 | {authenticationState === AuthenticationState.Unauthenticated && ( 73 |
74 | {' '} 77 | 80 |
81 | )} 82 | 83 |
84 | {sampleBox} 85 |
86 |

Authenticated Values

87 |

When logged in, this box will show your tokens and user info

88 | {accountInfo && ( 89 |
90 |

91 | ID Token: {accountInfo.jwtIdToken} 92 |

93 |

94 | Username: {accountInfo.account.userName} 95 |

96 |

97 | Access Token: {accountInfo.jwtAccessToken} 98 |

99 |

100 | Name: {accountInfo.account.name} 101 |

102 |
103 | )} 104 |
105 |
106 |

Errors

107 |

If authentication fails, this box will have the errors that occurred

108 | {error && ( 109 |
110 |

111 | errorCode: {error.errorCode} 112 |

113 |

114 | errorMessage: {error.errorMessage} 115 |

116 |
117 | )} 118 |
119 |
120 |
121 | ); 122 | }} 123 |
124 |
125 | ); 126 | } 127 | } 128 | 129 | export default App; 130 | -------------------------------------------------------------------------------- /samples/react-javascript/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /samples/react-javascript/src/GetAccessTokenButton.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default function GetTokenButton({ provider }) { 4 | const getAuthToken = () => { 5 | // You should should use getAccessToken() to fetch a fresh token before making API calls 6 | provider.getAccessToken().then(token => { 7 | alert(token.accessToken); 8 | }); 9 | }; 10 | 11 | return ( 12 |
13 |

14 | You can use the auth provider to get a fresh token. If a valid token is in cache it will be returned, otherwise 15 | a fresh token will be requested. If the request fails, the user will be forced to login again. 16 |

17 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /samples/react-javascript/src/GetIdTokenButton.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default function GetTokenButton({ provider }) { 4 | const getAuthToken = () => { 5 | provider.getIdToken().then(token => { 6 | alert(token.idToken.rawIdToken); 7 | }); 8 | }; 9 | 10 | return ( 11 |
12 |

13 | It's also possible to renew the IdToken. If a valid token is in the cache, it will be returned. Otherwise a 14 | renewed token will be requested. If the request fails, the user will be forced to login again. 15 |

16 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /samples/react-javascript/src/SampleAppButtonLaunch.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AzureAD, LoginType, AuthenticationState } from 'react-aad-msal'; 3 | import { basicReduxStore } from './reduxStore'; 4 | import GetAccessTokenButton from './GetAccessTokenButton'; 5 | import GetIdTokenButton from './GetIdTokenButton'; 6 | 7 | // Import the authentication provider which holds the default settings 8 | import { authProvider } from './authProvider'; 9 | 10 | class SampleAppButtonLaunch extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | // Change the login type to execute in a Popup 15 | const options = authProvider.getProviderOptions(); 16 | options.loginType = LoginType.Popup; 17 | authProvider.setProviderOptions(options); 18 | } 19 | 20 | render() { 21 | return ( 22 | 23 | {({ login, logout, authenticationState }) => { 24 | const isInProgress = authenticationState === AuthenticationState.InProgress; 25 | const isAuthenticated = authenticationState === AuthenticationState.Authenticated; 26 | const isUnauthenticated = authenticationState === AuthenticationState.Unauthenticated; 27 | 28 | if (isAuthenticated) { 29 | return ( 30 | 31 |

You're logged in!

32 | 35 | 36 | 37 |
38 | ); 39 | } else if (isUnauthenticated || isInProgress) { 40 | return ( 41 | 44 | ); 45 | } 46 | }} 47 |
48 | ); 49 | } 50 | } 51 | export default SampleAppButtonLaunch; 52 | -------------------------------------------------------------------------------- /samples/react-javascript/src/SampleAppRedirectOnLaunch.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AzureAD, LoginType, AuthenticationState } from 'react-aad-msal'; 3 | 4 | import { basicReduxStore } from './reduxStore'; 5 | import GetAccessTokenButton from './GetAccessTokenButton'; 6 | import GetIdTokenButton from './GetIdTokenButton'; 7 | 8 | // Import the authentication provider which holds the default settings 9 | import { authProvider } from './authProvider'; 10 | 11 | class SampleAppRedirectOnLaunch extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | 15 | // Change the login type to execute in a Redirect 16 | const options = authProvider.getProviderOptions(); 17 | options.loginType = LoginType.Redirect; 18 | authProvider.setProviderOptions(options); 19 | 20 | this.interval = null; 21 | let redirectEnabled = sessionStorage.getItem('redirectEnabled') || false; 22 | this.state = { 23 | counter: 5, 24 | redirectEnabled: redirectEnabled, 25 | }; 26 | } 27 | 28 | handleCheck = () => { 29 | this.setState( 30 | state => ({ 31 | ...state, 32 | redirectEnabled: !state.redirectEnabled, 33 | }), 34 | () => { 35 | if (!this.state.redirectEnabled) { 36 | this.clearRedirectInterval(); 37 | } else { 38 | sessionStorage.setItem('redirectEnabled', true); 39 | } 40 | }, 41 | ); 42 | }; 43 | 44 | countdownToLogin = loginFunction => { 45 | if (this.state.redirectEnabled && !this.interval) { 46 | this.interval = setInterval(() => { 47 | if (this.state.counter > 0) { 48 | this.setState({ counter: this.state.counter - 1 }); 49 | } else { 50 | this.clearRedirectInterval(); 51 | this.setState({ redirectEnabled: false }); 52 | loginFunction(); 53 | } 54 | }, 1000); 55 | } 56 | }; 57 | 58 | clearRedirectInterval() { 59 | clearInterval(this.interval); 60 | this.setState({ counter: 5 }); 61 | sessionStorage.removeItem('redirectEnabled'); 62 | this.interval = null; 63 | } 64 | 65 | render() { 66 | const { redirectEnabled } = this.state; 67 | 68 | return ( 69 | 70 | {({ login, logout, authenticationState }) => { 71 | const isInProgress = authenticationState === AuthenticationState.InProgress; 72 | const isAuthenticated = authenticationState === AuthenticationState.Authenticated; 73 | const isUnauthenticated = authenticationState === AuthenticationState.Unauthenticated; 74 | 75 | if (isAuthenticated) { 76 | return ( 77 | 78 |

You're logged in!

79 | 82 | 83 | 84 |
85 | ); 86 | } else if (isUnauthenticated || isInProgress) { 87 | this.countdownToLogin(login); 88 | return ( 89 |
90 | Enable redirect 91 | {redirectEnabled &&

Redirecting in {this.state.counter} seconds...

} 92 |
93 | ); 94 | } 95 | }} 96 |
97 | ); 98 | } 99 | } 100 | 101 | export default SampleAppRedirectOnLaunch; 102 | -------------------------------------------------------------------------------- /samples/react-javascript/src/authProvider.js: -------------------------------------------------------------------------------- 1 | import { MsalAuthProvider, LoginType } from "react-aad-msal"; 2 | import { Logger, LogLevel } from "msal"; 3 | 4 | // The auth provider should be a singleton. Best practice is to only have it ever instantiated once. 5 | // Avoid creating an instance inside the component it will be recreated on each render. 6 | // If two providers are created on the same page it will cause authentication errors. 7 | export const authProvider = new MsalAuthProvider( 8 | { 9 | auth: { 10 | authority: "https://login.microsoftonline.com/common", 11 | clientId: "0f2c6253-3928-4fea-b131-bf6ef8f69e9c", 12 | postLogoutRedirectUri: window.location.origin, 13 | redirectUri: window.location.origin, 14 | validateAuthority: true, 15 | 16 | // After being redirected to the "redirectUri" page, should user 17 | // be redirected back to the Url where their login originated from? 18 | navigateToLoginRequestUrl: false 19 | }, 20 | // Enable logging of MSAL events for easier troubleshooting. 21 | // This should be disabled in production builds. 22 | system: { 23 | logger: new Logger( 24 | (logLevel, message, containsPii) => { 25 | console.log("[MSAL]", message); 26 | }, 27 | { 28 | level: LogLevel.Verbose, 29 | piiLoggingEnabled: false 30 | } 31 | ) 32 | }, 33 | cache: { 34 | cacheLocation: "sessionStorage", 35 | storeAuthStateInCookie: true 36 | } 37 | }, 38 | { 39 | scopes: ["openid"] 40 | }, 41 | { 42 | loginType: LoginType.Popup, 43 | // When a token is refreshed it will be done by loading a page in an iframe. 44 | // Rather than reloading the same page, we can point to an empty html file which will prevent 45 | // site resources from being loaded twice. 46 | tokenRefreshUri: window.location.origin + "/auth.html" 47 | } 48 | ); 49 | -------------------------------------------------------------------------------- /samples/react-javascript/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /samples/react-javascript/src/index.js: -------------------------------------------------------------------------------- 1 | import "react-app-polyfill/ie11"; 2 | import "react-app-polyfill/stable"; 3 | 4 | import React from "react"; 5 | import ReactDOM from "react-dom"; 6 | import "./index.css"; 7 | import App from "./App"; 8 | import registerServiceWorker from "./registerServiceWorker"; 9 | import { Provider } from "react-redux"; 10 | import { basicReduxStore } from "./reduxStore"; 11 | 12 | ReactDOM.render( 13 | 14 | 15 | , 16 | document.getElementById("root") 17 | ); 18 | registerServiceWorker(); 19 | -------------------------------------------------------------------------------- /samples/react-javascript/src/reduxStore.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import { AuthenticationActions, AuthenticationState } from 'react-aad-msal'; 3 | 4 | const initialState = { 5 | initializing: false, 6 | initialized: false, 7 | idToken: null, 8 | accessToken: null, 9 | state: AuthenticationState.Unauthenticated, 10 | }; 11 | 12 | const rootReducer = (state = initialState, action) => { 13 | switch (action.type) { 14 | case AuthenticationActions.Initializing: 15 | return { 16 | ...state, 17 | initializing: true, 18 | initialized: false, 19 | }; 20 | case AuthenticationActions.Initialized: 21 | return { 22 | ...state, 23 | initializing: false, 24 | initialized: true, 25 | }; 26 | case AuthenticationActions.AcquiredIdTokenSuccess: 27 | return { 28 | ...state, 29 | idToken: action.payload, 30 | }; 31 | case AuthenticationActions.AcquiredAccessTokenSuccess: 32 | return { 33 | ...state, 34 | accessToken: action.payload, 35 | }; 36 | case AuthenticationActions.AcquiredAccessTokenError: 37 | return { 38 | ...state, 39 | accessToken: null, 40 | }; 41 | case AuthenticationActions.LoginSuccess: 42 | return { 43 | ...state, 44 | account: action.payload.account, 45 | }; 46 | case AuthenticationActions.LoginError: 47 | case AuthenticationActions.AcquiredIdTokenError: 48 | case AuthenticationActions.LogoutSuccess: 49 | return { ...state, idToken: null, accessToken: null, account: null }; 50 | case AuthenticationActions.AuthenticatedStateChanged: 51 | return { 52 | ...state, 53 | state: action.payload, 54 | }; 55 | default: 56 | return state; 57 | } 58 | }; 59 | 60 | export const basicReduxStore = createStore( 61 | rootReducer, 62 | // Enable the Redux DevTools extension if available 63 | /// See more: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfiblj 64 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), 65 | ); 66 | -------------------------------------------------------------------------------- /samples/react-javascript/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/), 17 | ); 18 | 19 | export default function register() { 20 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 21 | // The URL constructor is available in all browsers that support SW. 22 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 23 | if (publicUrl.origin !== window.location.origin) { 24 | // Our service worker won't work if PUBLIC_URL is on a different origin 25 | // from what our page is served on. This might happen if a CDN is used to 26 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 27 | return; 28 | } 29 | 30 | window.addEventListener('load', () => { 31 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 32 | 33 | if (isLocalhost) { 34 | // This is running on localhost. Lets check if a service worker still exists or not. 35 | checkValidServiceWorker(swUrl); 36 | 37 | // Add some additional logging to localhost, pointing developers to the 38 | // service worker/PWA documentation. 39 | navigator.serviceWorker.ready.then(() => { 40 | console.log( 41 | 'This web app is being served cache-first by a service ' + 42 | 'worker. To learn more, visit https://goo.gl/SC7cgQ', 43 | ); 44 | }); 45 | } else { 46 | // Is not local host. Just register service worker 47 | registerValidSW(swUrl); 48 | } 49 | }); 50 | } 51 | } 52 | 53 | function registerValidSW(swUrl) { 54 | navigator.serviceWorker 55 | .register(swUrl) 56 | .then(registration => { 57 | registration.onupdatefound = () => { 58 | const installingWorker = registration.installing; 59 | installingWorker.onstatechange = () => { 60 | if (installingWorker.state === 'installed') { 61 | if (navigator.serviceWorker.controller) { 62 | // At this point, the old content will have been purged and 63 | // the fresh content will have been added to the cache. 64 | // It's the perfect time to display a "New content is 65 | // available; please refresh." message in your web app. 66 | console.log('New content is available; please refresh.'); 67 | } else { 68 | // At this point, everything has been precached. 69 | // It's the perfect time to display a 70 | // "Content is cached for offline use." message. 71 | console.log('Content is cached for offline use.'); 72 | } 73 | } 74 | }; 75 | }; 76 | }) 77 | .catch(error => { 78 | console.error('Error during service worker registration:', error); 79 | }); 80 | } 81 | 82 | function checkValidServiceWorker(swUrl) { 83 | // Check if the service worker can be found. If it can't reload the page. 84 | fetch(swUrl) 85 | .then(response => { 86 | // Ensure service worker exists, and that we really are getting a JS file. 87 | if (response.status === 404 || response.headers.get('content-type').indexOf('javascript') === -1) { 88 | // No service worker found. Probably a different app. Reload the page. 89 | navigator.serviceWorker.ready.then(registration => { 90 | registration.unregister().then(() => { 91 | window.location.reload(); 92 | }); 93 | }); 94 | } else { 95 | // Service worker found. Proceed as normal. 96 | registerValidSW(swUrl); 97 | } 98 | }) 99 | .catch(() => { 100 | console.log('No internet connection found. App is running in offline mode.'); 101 | }); 102 | } 103 | 104 | export function unregister() { 105 | if ('serviceWorker' in navigator) { 106 | navigator.serviceWorker.ready.then(registration => { 107 | registration.unregister(); 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /samples/react-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-typescript-sample", 3 | "version": "1.0.0", 4 | "description": "A sample React application that demonstrates the Azure AD react component.", 5 | "private": true, 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/syncweek-react-aad/react-aad.git" 10 | }, 11 | "author": "Laura Bochenek", 12 | "contributors": [ 13 | "Laura Bochenek ", 14 | "Omeed Musavi ", 15 | "Lilian Kasem ", 16 | "Tess DiStefano ", 17 | "Lucas Huet-Hudson ", 18 | "Zach Miller ", 19 | "P.J. Little ", 20 | "Shawn Cicoria ", 21 | "Andrew Craswell ", 22 | "Björn Dalfors " 23 | ], 24 | "dependencies": { 25 | "@testing-library/jest-dom": "^4.2.4", 26 | "@testing-library/react": "^9.3.2", 27 | "@testing-library/user-event": "^7.1.2", 28 | "@types/jest": "^24.0.0", 29 | "@types/node": "^12.0.0", 30 | "@types/react": "^16.9.0", 31 | "@types/react-dom": "^16.9.0", 32 | "@types/react-redux": "^7.1.7", 33 | "msal": "^1.2.0", 34 | "react": "^16.12.0", 35 | "react-aad-msal": "^2.3.2", 36 | "react-dom": "^16.12.0", 37 | "react-redux": "^7.1.3", 38 | "react-scripts": "3.3.0", 39 | "redux": "^4.0.5", 40 | "typescript": "^3.7.5" 41 | }, 42 | "devDependencies": { 43 | "rimraf": "^3.0.0" 44 | }, 45 | "scripts": { 46 | "clean": "rimraf build", 47 | "start": "react-scripts start", 48 | "build": "react-scripts build", 49 | "test": "react-scripts test --env=jsdom", 50 | "eject": "react-scripts eject" 51 | }, 52 | "eslintConfig": { 53 | "extends": "react-app" 54 | }, 55 | "browserslist": { 56 | "production": [ 57 | ">0.2%", 58 | "not dead", 59 | "not ie < 11", 60 | "not op_mini all" 61 | ], 62 | "development": [ 63 | "last 1 chrome version", 64 | "last 1 firefox version", 65 | "last 1 safari version" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /samples/react-typescript/public/auth.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /samples/react-typescript/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syncweek-react-aad/react-aad/d12597f7fd3ba661cdc3f69d41301282124c353e/samples/react-typescript/public/favicon.ico -------------------------------------------------------------------------------- /samples/react-typescript/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /samples/react-typescript/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /samples/react-typescript/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-header { 6 | background-color: #222; 7 | height: 25px; 8 | padding: 20px; 9 | color: white; 10 | margin-bottom: 2em; 11 | } 12 | 13 | .App-title { 14 | font-size: 1.5em; 15 | margin: 0; 16 | } 17 | 18 | .App-intro { 19 | font-size: large; 20 | } 21 | 22 | .SampleContainer { 23 | width: 100%; 24 | display: flex; 25 | justify-content: center; 26 | } 27 | 28 | .SampleBox { 29 | border: 1px solid black; 30 | width: 20em; 31 | margin: 2em; 32 | padding: 10px; 33 | border-radius: 10px; 34 | } 35 | 36 | .SampleHeader { 37 | font-size: 1.25em; 38 | } 39 | 40 | .Button { 41 | background-color: #00a1f1; 42 | border: none; 43 | color: white; 44 | display: inline-block; 45 | padding: 15px; 46 | font-size: 15px; 47 | cursor: pointer; 48 | } 49 | 50 | .Button:disabled { 51 | background-color: grey; 52 | } 53 | -------------------------------------------------------------------------------- /samples/react-typescript/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | it("renders without crashing", () => { 6 | const div = document.createElement("div"); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /samples/react-typescript/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | AzureAD, 4 | AuthenticationState, 5 | IAzureADFunctionProps 6 | } from "react-aad-msal"; 7 | import { basicReduxStore } from "./reduxStore"; 8 | 9 | // Import the authentication provider which holds the default settings 10 | import { authProvider } from "./authProvider"; 11 | 12 | import "./App.css"; 13 | import { SampleBox } from "./SampleBox"; 14 | 15 | const App = () => { 16 | const [sampleType, setSampleType] = useState(""); 17 | 18 | const handleClick = (sampleType: string) => { 19 | setSampleType(sampleType); 20 | localStorage.setItem("sampleType", sampleType); 21 | }; 22 | 23 | return ( 24 |
25 |
26 |

Welcome to the react-aad-msal sample

27 |
28 | 29 | 30 | {({ 31 | accountInfo, 32 | authenticationState, 33 | error 34 | }: IAzureADFunctionProps) => { 35 | return ( 36 | 37 | {authenticationState === AuthenticationState.Unauthenticated && ( 38 |
39 | {" "} 45 | 51 |
52 | )} 53 | 54 |
55 | 56 |
57 |

Authenticated Values

58 |

59 | When logged in, this box will show your tokens and user info 60 |

61 | {accountInfo && ( 62 |
63 |

64 | ID Token:{" "} 65 | {accountInfo.jwtIdToken} 66 |

67 |

68 | Username:{" "} 69 | {accountInfo.account.userName} 70 |

71 |

72 | 73 | Access Token: 74 | {" "} 75 | {accountInfo.jwtAccessToken} 76 |

77 |

78 | Name:{" "} 79 | {accountInfo.account.name} 80 |

81 |
82 | )} 83 |
84 |
85 |

Errors

86 |

87 | If authentication fails, this box will have the errors that 88 | occurred 89 |

90 | {error && ( 91 |
92 |

93 | errorCode:{" "} 94 | {error.errorCode} 95 |

96 |

97 | 98 | errorMessage: 99 | {" "} 100 | {error.errorMessage} 101 |

102 |
103 | )} 104 |
105 |
106 |
107 | ); 108 | }} 109 |
110 |
111 | ); 112 | }; 113 | 114 | export default App; 115 | -------------------------------------------------------------------------------- /samples/react-typescript/src/GetAccessTokenButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { MsalAuthProvider } from "react-aad-msal"; 3 | 4 | interface Props { 5 | provider: MsalAuthProvider; 6 | } 7 | 8 | const GetTokenButton = ({ provider }: Props) => { 9 | const getAuthToken = async () => { 10 | alert((await provider.getAccessToken()).accessToken); 11 | }; 12 | 13 | return ( 14 |
15 |

16 | You can use the auth provider to get a fresh token. If a valid token is 17 | in cache it will be returned, otherwise a fresh token will be requested. 18 | If the request fails, the user will be forced to login again. 19 |

20 | 23 |
24 | ); 25 | }; 26 | 27 | export default GetTokenButton; 28 | -------------------------------------------------------------------------------- /samples/react-typescript/src/GetIdTokenButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { MsalAuthProvider } from "react-aad-msal"; 3 | 4 | interface Props { 5 | provider: MsalAuthProvider; 6 | } 7 | 8 | const GetTokenButton = ({ provider }: Props) => { 9 | const getAuthToken = async () => { 10 | alert((await provider.getIdToken()).idToken.rawIdToken); 11 | }; 12 | 13 | return ( 14 |
15 |

16 | It's also possible to renew the IdToken. If a valid token is in the 17 | cache, it will be returned. Otherwise a renewed token will be requested. 18 | If the request fails, the user will be forced to login again. 19 |

20 | 23 |
24 | ); 25 | }; 26 | 27 | export default GetTokenButton; 28 | -------------------------------------------------------------------------------- /samples/react-typescript/src/SampleAppButtonLaunch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | AzureAD, 4 | LoginType, 5 | AuthenticationState, 6 | IAzureADFunctionProps 7 | } from "react-aad-msal"; 8 | import { basicReduxStore } from "./reduxStore"; 9 | import GetAccessTokenButton from "./GetAccessTokenButton"; 10 | import GetIdTokenButton from "./GetIdTokenButton"; 11 | 12 | // Import the authentication provider which holds the default settings 13 | import { authProvider } from "./authProvider"; 14 | 15 | const SampleAppButtonLaunch = () => { 16 | // Change the login type to execute in a Popup 17 | const options = authProvider.getProviderOptions(); 18 | options.loginType = LoginType.Popup; 19 | authProvider.setProviderOptions(options); 20 | 21 | return ( 22 | 23 | {({ login, logout, authenticationState }: IAzureADFunctionProps) => { 24 | const isInProgress = 25 | authenticationState === AuthenticationState.InProgress; 26 | const isAuthenticated = 27 | authenticationState === AuthenticationState.Authenticated; 28 | const isUnauthenticated = 29 | authenticationState === AuthenticationState.Unauthenticated; 30 | 31 | if (isAuthenticated) { 32 | return ( 33 | 34 |

You're logged in!

35 | 38 | 39 | 40 |
41 | ); 42 | } else if (isUnauthenticated || isInProgress) { 43 | return ( 44 | 47 | ); 48 | } 49 | }} 50 |
51 | ); 52 | }; 53 | 54 | export default SampleAppButtonLaunch; 55 | -------------------------------------------------------------------------------- /samples/react-typescript/src/SampleAppRedirectOnLaunch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | AzureAD, 4 | LoginType, 5 | AuthenticationState, 6 | IAzureADFunctionProps 7 | } from "react-aad-msal"; 8 | 9 | import { basicReduxStore } from "./reduxStore"; 10 | import GetAccessTokenButton from "./GetAccessTokenButton"; 11 | import GetIdTokenButton from "./GetIdTokenButton"; 12 | 13 | // Import the authentication provider which holds the default settings 14 | import { authProvider } from "./authProvider"; 15 | 16 | const SampleAppRedirectOnLaunch = () => { 17 | const [counter, setCounter] = React.useState(5); 18 | const [redirectEnabled, setRedirectEnabled] = React.useState( 19 | sessionStorage.getItem("redirectEnabled") === "true" || false 20 | ); 21 | 22 | const [login, setLogin] = React.useState(); 23 | 24 | const clearRedirectInterval = React.useCallback(interval => { 25 | clearInterval(interval); 26 | setCounter(5); 27 | sessionStorage.removeItem("redirectEnabled"); 28 | }, []); 29 | 30 | React.useEffect(() => { 31 | let interval: any; 32 | if (redirectEnabled) { 33 | sessionStorage.setItem("redirectEnabled", "true"); 34 | interval = setInterval(() => { 35 | if (counter > 0) { 36 | setCounter(c => c - 1); 37 | } else { 38 | clearRedirectInterval(interval); 39 | setRedirectEnabled(false); 40 | login(); 41 | } 42 | clearInterval(interval); 43 | }, 1000); 44 | } 45 | return () => { 46 | clearInterval(interval); 47 | }; 48 | }, [counter, clearRedirectInterval, redirectEnabled, login]); 49 | 50 | // Change the login type to execute in a Redirect 51 | const options = authProvider.getProviderOptions(); 52 | options.loginType = LoginType.Redirect; 53 | authProvider.setProviderOptions(options); 54 | 55 | const handleCheck = () => { 56 | setRedirectEnabled(r => !r); 57 | if (redirectEnabled) { 58 | sessionStorage.removeItem("redirectEnabled"); 59 | } else { 60 | setCounter(5); 61 | } 62 | }; 63 | 64 | const setLoginObject = (loginFunction: any) => { 65 | setLogin(() => loginFunction); 66 | }; 67 | 68 | return ( 69 | 70 | {({ login, logout, authenticationState }: IAzureADFunctionProps) => { 71 | const isInProgress = 72 | authenticationState === AuthenticationState.InProgress; 73 | const isAuthenticated = 74 | authenticationState === AuthenticationState.Authenticated; 75 | const isUnauthenticated = 76 | authenticationState === AuthenticationState.Unauthenticated; 77 | 78 | if (isAuthenticated) { 79 | return ( 80 | 81 |

You're logged in!

82 | 85 | 86 | 87 |
88 | ); 89 | } else if (isUnauthenticated || isInProgress) { 90 | setLoginObject(login); 91 | return ( 92 |
93 | {" "} 98 | Enable redirect : {redirectEnabled} 99 | {redirectEnabled &&

Redirecting in {counter} seconds...

} 100 |
101 | ); 102 | } 103 | }} 104 |
105 | ); 106 | }; 107 | 108 | export default SampleAppRedirectOnLaunch; 109 | -------------------------------------------------------------------------------- /samples/react-typescript/src/SampleBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SampleAppButtonLaunch from "./SampleAppButtonLaunch"; 3 | import SampleAppRedirectOnLaunch from "./SampleAppRedirectOnLaunch"; 4 | 5 | export const SampleBox: React.FC<{ 6 | sampleType: string; 7 | }> = ({ sampleType }) => 8 | sampleType === "popup" ? ( 9 |
10 |

Button Login

11 |

12 | example will launch a popup dialog to allow for authentication with 13 | Azure Active Directory 14 |

15 | 16 |
17 | ) : ( 18 |
19 |

Automatic Redirect

20 |

21 | example shows how you can use the AzureAD component to redirect the reen 22 | automatically on page load.Click the checkbox below to enable the 23 | redirect and refresh your browser window. 24 |

25 | 26 |
27 | ); 28 | -------------------------------------------------------------------------------- /samples/react-typescript/src/authProvider.ts: -------------------------------------------------------------------------------- 1 | import { MsalAuthProvider, LoginType } from "react-aad-msal"; 2 | import { LogLevel, Logger } from "msal"; 3 | 4 | const logger = new Logger( 5 | (logLevel, message, containsPii) => { 6 | console.log("[MSAL]", message); 7 | }, 8 | { 9 | level: LogLevel.Verbose, 10 | piiLoggingEnabled: false 11 | } 12 | ); 13 | 14 | // The auth provider should be a singleton. Best practice is to only have it ever instantiated once. 15 | // Avoid creating an instance inside the component it will be recreated on each render. 16 | // If two providers are created on the same page it will cause authentication errors. 17 | export const authProvider = new MsalAuthProvider( 18 | { 19 | auth: { 20 | authority: "https://login.microsoftonline.com/common", 21 | clientId: "0f2c6253-3928-4fea-b131-bf6ef8f69e9c", 22 | postLogoutRedirectUri: window.location.origin, 23 | redirectUri: window.location.origin, 24 | validateAuthority: true, 25 | 26 | // After being redirected to the "redirectUri" page, should user 27 | // be redirected back to the Url where their login originated from? 28 | navigateToLoginRequestUrl: false 29 | }, 30 | // Enable logging of MSAL events for easier troubleshooting. 31 | // This should be disabled in production builds. 32 | system: { 33 | logger: logger as any 34 | }, 35 | cache: { 36 | cacheLocation: "sessionStorage", 37 | storeAuthStateInCookie: false 38 | } 39 | }, 40 | { 41 | scopes: ["openid"] 42 | }, 43 | { 44 | loginType: LoginType.Popup, 45 | // When a token is refreshed it will be done by loading a page in an iframe. 46 | // Rather than reloading the same page, we can point to an empty html file which will prevent 47 | // site resources from being loaded twice. 48 | tokenRefreshUri: window.location.origin + "/auth.html" 49 | } 50 | ); 51 | -------------------------------------------------------------------------------- /samples/react-typescript/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /samples/react-typescript/src/index.tsx: -------------------------------------------------------------------------------- 1 | import "react-app-polyfill/ie11"; 2 | import "react-app-polyfill/stable"; 3 | 4 | import React from "react"; 5 | import ReactDOM from "react-dom"; 6 | import "./index.css"; 7 | import App from "./App"; 8 | import { Provider } from "react-redux"; 9 | import { basicReduxStore } from "./reduxStore"; 10 | 11 | import * as serviceWorker from "./serviceWorker"; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | , 17 | document.getElementById("root") 18 | ); 19 | 20 | // If you want your app to work offline and load faster, you can change 21 | // unregister() to register() below. Note this comes with some pitfalls. 22 | // Learn more about service workers: https://bit.ly/CRA-PWA 23 | serviceWorker.unregister(); 24 | -------------------------------------------------------------------------------- /samples/react-typescript/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /samples/react-typescript/src/reduxStore.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Reducer, compose } from "redux"; 2 | import { AuthenticationActions, AuthenticationState } from "react-aad-msal"; 3 | 4 | // add redux devtools extension as a property/method in window 5 | declare global { 6 | interface Window { 7 | __REDUX_DEVTOOLS_EXTENSION__?: typeof compose; 8 | } 9 | } 10 | 11 | const initialState = { 12 | initializing: false, 13 | initialized: false, 14 | idToken: null, 15 | accessToken: null, 16 | state: AuthenticationState.Unauthenticated 17 | }; 18 | 19 | const rootReducer: Reducer = ( 20 | state = initialState, 21 | action: { type: any; payload: { account: any } } 22 | ) => { 23 | switch (action.type) { 24 | case AuthenticationActions.Initializing: 25 | return { 26 | ...state, 27 | initializing: true, 28 | initialized: false 29 | }; 30 | case AuthenticationActions.Initialized: 31 | return { 32 | ...state, 33 | initializing: false, 34 | initialized: true 35 | }; 36 | case AuthenticationActions.AcquiredIdTokenSuccess: 37 | return { 38 | ...state, 39 | idToken: action.payload 40 | }; 41 | case AuthenticationActions.AcquiredAccessTokenSuccess: 42 | return { 43 | ...state, 44 | accessToken: action.payload 45 | }; 46 | case AuthenticationActions.AcquiredAccessTokenError: 47 | return { 48 | ...state, 49 | accessToken: null 50 | }; 51 | case AuthenticationActions.LoginSuccess: 52 | return { 53 | ...state, 54 | account: action.payload.account 55 | }; 56 | case AuthenticationActions.LoginError: 57 | case AuthenticationActions.AcquiredIdTokenError: 58 | case AuthenticationActions.LogoutSuccess: 59 | return { ...state, idToken: null, accessToken: null, account: null }; 60 | case AuthenticationActions.AuthenticatedStateChanged: 61 | return { 62 | ...state, 63 | state: action.payload 64 | }; 65 | default: 66 | return state; 67 | } 68 | }; 69 | 70 | export const basicReduxStore = createStore( 71 | rootReducer, 72 | // Enable the Redux DevTools extension if available 73 | /// See more: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfiblj 74 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 75 | ); 76 | -------------------------------------------------------------------------------- /samples/react-typescript/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === "localhost" || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === "[::1]" || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 32 | if (publicUrl.origin !== window.location.origin) { 33 | // Our service worker won't work if PUBLIC_URL is on a different origin 34 | // from what our page is served on. This might happen if a CDN is used to 35 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 36 | return; 37 | } 38 | 39 | window.addEventListener("load", () => { 40 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 41 | 42 | if (isLocalhost) { 43 | // This is running on localhost. Let's check if a service worker still exists or not. 44 | checkValidServiceWorker(swUrl, config); 45 | 46 | // Add some additional logging to localhost, pointing developers to the 47 | // service worker/PWA documentation. 48 | navigator.serviceWorker.ready.then(() => { 49 | console.log( 50 | "This web app is being served cache-first by a service " + 51 | "worker. To learn more, visit https://bit.ly/CRA-PWA" 52 | ); 53 | }); 54 | } else { 55 | // Is not localhost. Just register service worker 56 | registerValidSW(swUrl, config); 57 | } 58 | }); 59 | } 60 | } 61 | 62 | function registerValidSW(swUrl: string, config?: Config) { 63 | navigator.serviceWorker 64 | .register(swUrl) 65 | .then(registration => { 66 | registration.onupdatefound = () => { 67 | const installingWorker = registration.installing; 68 | if (installingWorker == null) { 69 | return; 70 | } 71 | installingWorker.onstatechange = () => { 72 | if (installingWorker.state === "installed") { 73 | if (navigator.serviceWorker.controller) { 74 | // At this point, the updated precached content has been fetched, 75 | // but the previous service worker will still serve the older 76 | // content until all client tabs are closed. 77 | console.log( 78 | "New content is available and will be used when all " + 79 | "tabs for this page are closed. See https://bit.ly/CRA-PWA." 80 | ); 81 | 82 | // Execute callback 83 | if (config && config.onUpdate) { 84 | config.onUpdate(registration); 85 | } 86 | } else { 87 | // At this point, everything has been precached. 88 | // It's the perfect time to display a 89 | // "Content is cached for offline use." message. 90 | console.log("Content is cached for offline use."); 91 | 92 | // Execute callback 93 | if (config && config.onSuccess) { 94 | config.onSuccess(registration); 95 | } 96 | } 97 | } 98 | }; 99 | }; 100 | }) 101 | .catch(error => { 102 | console.error("Error during service worker registration:", error); 103 | }); 104 | } 105 | 106 | function checkValidServiceWorker(swUrl: string, config?: Config) { 107 | // Check if the service worker can be found. If it can't reload the page. 108 | fetch(swUrl, { 109 | headers: { "Service-Worker": "script" } 110 | }) 111 | .then(response => { 112 | // Ensure service worker exists, and that we really are getting a JS file. 113 | const contentType = response.headers.get("content-type"); 114 | if ( 115 | response.status === 404 || 116 | (contentType != null && contentType.indexOf("javascript") === -1) 117 | ) { 118 | // No service worker found. Probably a different app. Reload the page. 119 | navigator.serviceWorker.ready.then(registration => { 120 | registration.unregister().then(() => { 121 | window.location.reload(); 122 | }); 123 | }); 124 | } else { 125 | // Service worker found. Proceed as normal. 126 | registerValidSW(swUrl, config); 127 | } 128 | }) 129 | .catch(() => { 130 | console.log( 131 | "No internet connection found. App is running in offline mode." 132 | ); 133 | }); 134 | } 135 | 136 | export function unregister() { 137 | if ("serviceWorker" in navigator) { 138 | navigator.serviceWorker.ready.then(registration => { 139 | registration.unregister(); 140 | }); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /samples/react-typescript/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom/extend-expect"; 6 | -------------------------------------------------------------------------------- /samples/react-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "noEmit": true, 19 | "jsx": "react", 20 | "isolatedModules": true 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------