├── .gitignore ├── .gitmodules ├── BlazorServerSample ├── App.razor ├── BlazorServerSample.csproj ├── Pages │ ├── Index.razor │ ├── LoggedOut.razor │ ├── Login.razor │ ├── Subpage.razor │ └── _Host.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Shared │ ├── MainLayout.razor │ └── MainLayout.razor.css ├── Startup.cs ├── _Imports.razor ├── appsettings.json ├── appsettings.local.json.md └── wwwroot │ └── favicon.ico ├── BlazorWasmSample ├── App.razor ├── BlazorWasmSample.csproj ├── Pages │ ├── Index.razor │ ├── LoggedOut.razor │ └── Subpage.razor ├── Program.cs ├── Properties │ └── launchSettings.json ├── Shared │ ├── MainLayout.razor │ └── MainLayout.razor.css ├── _Imports.razor └── wwwroot │ ├── .gitignore │ ├── appsettings.json.md │ ├── css │ ├── app.css │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ └── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ ├── css │ │ └── open-iconic-bootstrap.min.css │ │ └── fonts │ │ ├── open-iconic.eot │ │ ├── open-iconic.otf │ │ ├── open-iconic.svg │ │ ├── open-iconic.ttf │ │ └── open-iconic.woff │ ├── favicon.ico │ └── index.html ├── Blazorade-MSAL.sln ├── Blazorade.Msal ├── Blazorade-Icon-128.png ├── Blazorade.Msal.csproj ├── Components │ ├── LoginRedirectHandler.razor │ └── LoginRedirectHandler.razor.cs ├── Configuration │ ├── AuthorityType.cs │ ├── BlazoradeMsalOptions.cs │ ├── InteractiveLoginMode.cs │ ├── ServiceCollectionExtensionMethods.cs │ └── TokenCacheScope.cs ├── InternalExtensions.cs ├── Security │ ├── Account.cs │ ├── AuthenticationResult.cs │ └── LoginPrompt.cs ├── Services │ ├── BlazoradeMsalService.cs │ ├── BlazoradeRequestFactory.cs │ ├── CreateRequestOptions.cs │ └── TokenAcquisitionRequest.cs ├── _Imports.razor ├── readme.md └── wwwroot │ └── js │ └── blazoradeMsal.js ├── GraphClient ├── App.razor ├── GraphClient.csproj ├── Pages │ ├── Help.razor │ ├── Index.razor │ └── LoggedOut.razor ├── Program.cs ├── Properties │ └── launchSettings.json ├── Shared │ ├── MainLayout.razor │ ├── MainLayout.razor.css │ ├── NavMenu.razor │ └── NavMenu.razor.css ├── _Imports.razor ├── readme.md └── wwwroot │ ├── .gitignore │ ├── css │ ├── app.css │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ └── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ ├── css │ │ └── open-iconic-bootstrap.min.css │ │ └── fonts │ │ ├── open-iconic.eot │ │ ├── open-iconic.otf │ │ ├── open-iconic.svg │ │ ├── open-iconic.ttf │ │ └── open-iconic.woff │ ├── favicon.ico │ ├── icon-192.png │ ├── index.html │ └── sample-data │ └── weather.json ├── LICENSE ├── MsalTestConsole ├── MsalTestConsole.csproj ├── Program.cs └── Properties │ └── launchSettings.json ├── README.md └── SharedComponentsSample ├── NavMenu.razor ├── SharedComponentsSample.csproj ├── TokenView.razor ├── _Imports.razor └── wwwroot ├── background.png └── exampleJsInterop.js /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | appsettings.local.json 7 | ServiceDependencies/ 8 | 9 | # User-specific files 10 | *.rsuser 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Mono auto generated files 20 | mono_crash.* 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | [Aa][Rr][Mm]/ 30 | [Aa][Rr][Mm]64/ 31 | bld/ 32 | [Bb]in/ 33 | [Oo]bj/ 34 | [Ll]og/ 35 | [Ll]ogs/ 36 | 37 | # Visual Studio 2015/2017 cache/options directory 38 | .vs/ 39 | # Uncomment if you have tasks that create the project's static files in wwwroot 40 | #wwwroot/ 41 | 42 | # Visual Studio 2017 auto generated files 43 | Generated\ Files/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUnit 50 | *.VisualState.xml 51 | TestResult.xml 52 | nunit-*.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | 67 | # StyleCop 68 | StyleCopReport.xml 69 | 70 | # Files built by Visual Studio 71 | *_i.c 72 | *_p.c 73 | *_h.h 74 | *.ilk 75 | *.meta 76 | *.obj 77 | *.iobj 78 | *.pch 79 | *.pdb 80 | *.ipdb 81 | *.pgc 82 | *.pgd 83 | *.rsp 84 | *.sbr 85 | *.tlb 86 | *.tli 87 | *.tlh 88 | *.tmp 89 | *.tmp_proj 90 | *_wpftmp.csproj 91 | *.log 92 | *.vspscc 93 | *.vssscc 94 | .builds 95 | *.pidb 96 | *.svclog 97 | *.scc 98 | 99 | # Chutzpah Test files 100 | _Chutzpah* 101 | 102 | # Visual C++ cache files 103 | ipch/ 104 | *.aps 105 | *.ncb 106 | *.opendb 107 | *.opensdf 108 | *.sdf 109 | *.cachefile 110 | *.VC.db 111 | *.VC.VC.opendb 112 | 113 | # Visual Studio profiler 114 | *.psess 115 | *.vsp 116 | *.vspx 117 | *.sap 118 | 119 | # Visual Studio Trace Files 120 | *.e2e 121 | 122 | # TFS 2012 Local Workspace 123 | $tf/ 124 | 125 | # Guidance Automation Toolkit 126 | *.gpState 127 | 128 | # ReSharper is a .NET coding add-in 129 | _ReSharper*/ 130 | *.[Rr]e[Ss]harper 131 | *.DotSettings.user 132 | 133 | # TeamCity is a build add-in 134 | _TeamCity* 135 | 136 | # DotCover is a Code Coverage Tool 137 | *.dotCover 138 | 139 | # AxoCover is a Code Coverage Tool 140 | .axoCover/* 141 | !.axoCover/settings.json 142 | 143 | # Visual Studio code coverage results 144 | *.coverage 145 | *.coveragexml 146 | 147 | # NCrunch 148 | _NCrunch_* 149 | .*crunch*.local.xml 150 | nCrunchTemp_* 151 | 152 | # MightyMoose 153 | *.mm.* 154 | AutoTest.Net/ 155 | 156 | # Web workbench (sass) 157 | .sass-cache/ 158 | 159 | # Installshield output folder 160 | [Ee]xpress/ 161 | 162 | # DocProject is a documentation generator add-in 163 | DocProject/buildhelp/ 164 | DocProject/Help/*.HxT 165 | DocProject/Help/*.HxC 166 | DocProject/Help/*.hhc 167 | DocProject/Help/*.hhk 168 | DocProject/Help/*.hhp 169 | DocProject/Help/Html2 170 | DocProject/Help/html 171 | 172 | # Click-Once directory 173 | publish/ 174 | 175 | # Publish Web Output 176 | *.[Pp]ublish.xml 177 | *.azurePubxml 178 | # Note: Comment the next line if you want to checkin your web deploy settings, 179 | # but database connection strings (with potential passwords) will be unencrypted 180 | *.pubxml 181 | *.publishproj 182 | 183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 184 | # checkin your Azure Web App publish settings, but sensitive information contained 185 | # in these scripts will be unencrypted 186 | PublishScripts/ 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # NuGet Symbol Packages 191 | *.snupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/[Pp]ackages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/[Pp]ackages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/[Pp]ackages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | *.appxbundle 217 | *.appxupload 218 | 219 | # Visual Studio cache files 220 | # files ending in .cache can be ignored 221 | *.[Cc]ache 222 | # but keep track of directories ending in .cache 223 | !?*.[Cc]ache/ 224 | 225 | # Others 226 | ClientBin/ 227 | ~$* 228 | *~ 229 | *.dbmdl 230 | *.dbproj.schemaview 231 | *.jfm 232 | *.pfx 233 | *.publishsettings 234 | orleans.codegen.cs 235 | 236 | # Including strong name files can present a security risk 237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 238 | #*.snk 239 | 240 | # Since there are multiple workflows, uncomment next line to ignore bower_components 241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 242 | #bower_components/ 243 | 244 | # RIA/Silverlight projects 245 | Generated_Code/ 246 | 247 | # Backup & report files from converting an old project file 248 | # to a newer Visual Studio version. Backup files are not needed, 249 | # because we have git ;-) 250 | _UpgradeReport_Files/ 251 | Backup*/ 252 | UpgradeLog*.XML 253 | UpgradeLog*.htm 254 | ServiceFabricBackup/ 255 | *.rptproj.bak 256 | 257 | # SQL Server files 258 | *.mdf 259 | *.ldf 260 | *.ndf 261 | 262 | # Business Intelligence projects 263 | *.rdl.data 264 | *.bim.layout 265 | *.bim_*.settings 266 | *.rptproj.rsuser 267 | *- [Bb]ackup.rdl 268 | *- [Bb]ackup ([0-9]).rdl 269 | *- [Bb]ackup ([0-9][0-9]).rdl 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # CodeRush personal settings 306 | .cr/personal 307 | 308 | # Python Tools for Visual Studio (PTVS) 309 | __pycache__/ 310 | *.pyc 311 | 312 | # Cake - Uncomment if you are using it 313 | # tools/** 314 | # !tools/packages.config 315 | 316 | # Tabs Studio 317 | *.tss 318 | 319 | # Telerik's JustMock configuration file 320 | *.jmconfig 321 | 322 | # BizTalk build output 323 | *.btp.cs 324 | *.btm.cs 325 | *.odx.cs 326 | *.xsd.cs 327 | 328 | # OpenCover UI analysis results 329 | OpenCover/ 330 | 331 | # Azure Stream Analytics local run output 332 | ASALocalRun/ 333 | 334 | # MSBuild Binary and Structured Log 335 | *.binlog 336 | 337 | # NVidia Nsight GPU debugger configuration file 338 | *.nvuser 339 | 340 | # MFractors (Xamarin productivity tool) working folder 341 | .mfractor/ 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ 354 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Blazorade-Core"] 2 | path = Blazorade-Core 3 | url = https://github.com/Blazorade/Blazorade-Core.git 4 | -------------------------------------------------------------------------------- /BlazorServerSample/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /BlazorServerSample/BlazorServerSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | Never 25 | 26 | 27 | PreserveNewest 28 | Never 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /BlazorServerSample/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 |

Home

4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /BlazorServerSample/Pages/LoggedOut.razor: -------------------------------------------------------------------------------- 1 | @page "/loggedout" 2 | 3 |

Logged Out

4 | Home -------------------------------------------------------------------------------- /BlazorServerSample/Pages/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/login" 2 | @inject NavigationManager navMan; 3 | 4 | @code{ 5 | private Exception ex; 6 | private void LoginSucceeded(AuthenticationResult token) 7 | { 8 | this.navMan.NavigateTo("/"); 9 | } 10 | 11 | private void LoginFailed(Exception ex) 12 | { 13 | this.ex = ex; 14 | this.StateHasChanged(); 15 | } 16 | } 17 |

Login

18 | Home 19 | 20 | 24 | 25 | @if(null != this.ex) 26 | { 27 |
@this.ex.ToString()
28 | } 29 | -------------------------------------------------------------------------------- /BlazorServerSample/Pages/Subpage.razor: -------------------------------------------------------------------------------- 1 | @page "/subpage" 2 | 3 |

Subpage

4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /BlazorServerSample/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace BlazorServerSample.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = null; 6 | } 7 | 8 | 9 | 10 | 11 | 12 | 13 | Blazorade MSAL Server Demo 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /BlazorServerSample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace BlazorServerSample 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureAppConfiguration(builder => 22 | { 23 | builder.AddJsonFile("appsettings.local.json", optional: true); 24 | }) 25 | .ConfigureWebHostDefaults(webBuilder => 26 | { 27 | webBuilder.UseStartup(); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BlazorServerSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52268", 7 | "sslPort": 44336 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorServerSample": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BlazorServerSample/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | @Body 5 |
6 | -------------------------------------------------------------------------------- /BlazorServerSample/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | .main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | } 28 | 29 | .top-row a:first-child { 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | } 33 | 34 | @media (max-width: 640.98px) { 35 | .top-row:not(.auth) { 36 | display: none; 37 | } 38 | 39 | .top-row.auth { 40 | justify-content: space-between; 41 | } 42 | 43 | .top-row a, .top-row .btn-link { 44 | margin-left: 0; 45 | } 46 | } 47 | 48 | @media (min-width: 641px) { 49 | .page { 50 | flex-direction: row; 51 | } 52 | 53 | .sidebar { 54 | width: 250px; 55 | height: 100vh; 56 | position: sticky; 57 | top: 0; 58 | } 59 | 60 | .top-row { 61 | position: sticky; 62 | top: 0; 63 | z-index: 1; 64 | } 65 | 66 | .main > div { 67 | padding-left: 2rem !important; 68 | padding-right: 1.5rem !important; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /BlazorServerSample/Startup.cs: -------------------------------------------------------------------------------- 1 | using Blazorade.Msal.Configuration; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.HttpsPolicy; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | 14 | namespace BlazorServerSample 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | services 30 | .AddRazorPages().Services 31 | .AddServerSideBlazor().Services 32 | .AddBlazoradeMsal((sp, o) => 33 | { 34 | var root = sp.GetService(); 35 | root.GetSection("app").Bind(o); 36 | 37 | o.DefaultScopes = new string[] { "openid", "profile" }; 38 | o.PostLogoutUrl = "/loggedout"; 39 | o.RedirectUrl = "/login"; 40 | o.InteractiveLoginMode = InteractiveLoginMode.Redirect; 41 | o.TokenCacheScope = TokenCacheScope.Persistent; 42 | }) 43 | ; 44 | } 45 | 46 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 47 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 48 | { 49 | if (env.IsDevelopment()) 50 | { 51 | app.UseDeveloperExceptionPage(); 52 | } 53 | else 54 | { 55 | app.UseExceptionHandler("/Error"); 56 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 57 | app.UseHsts(); 58 | } 59 | 60 | app.UseHttpsRedirection(); 61 | app.UseStaticFiles(); 62 | 63 | app.UseRouting(); 64 | 65 | app.UseEndpoints(endpoints => 66 | { 67 | endpoints.MapBlazorHub(); 68 | endpoints.MapFallbackToPage("/_Host"); 69 | }); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /BlazorServerSample/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using BlazorServerSample 10 | @using BlazorServerSample.Shared 11 | 12 | @using SharedComponentsSample 13 | 14 | @using Blazorade.Core 15 | @using Blazorade.Core.Components 16 | @using Blazorade.Core.Components.Server 17 | @using Blazorade.Core.Interop 18 | @using Blazorade.Msal 19 | @using Blazorade.Msal.Components 20 | @using Blazorade.Msal.Configuration 21 | @using Blazorade.Msal.Security 22 | @using Blazorade.Msal.Services 23 | -------------------------------------------------------------------------------- /BlazorServerSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft": "Warning", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BlazorServerSample/appsettings.local.json.md: -------------------------------------------------------------------------------- 1 | # appsettings.local.json 2 | 3 | In order to be able to run this sample application locally, you need to make sure that you have a `appsettings.local.json` file in the same folder with this documentation. That file is excluded from source control. 4 | 5 | This file should have the following contents. 6 | 7 | ``` JSON 8 | { 9 | "app": { 10 | "clientId": "", 11 | "tenantId": ".onmicrosoft.com" 12 | } 13 | } 14 | ``` 15 | 16 | The `clientId` attribute contains the application ID (client ID) of the application that you have registered in Azure AD. This is the identity of your application. 17 | 18 | The `tenantId` attribute contains the name or ID of the tenant the application is registered in. 19 | -------------------------------------------------------------------------------- /BlazorServerSample/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/BlazorServerSample/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlazorWasmSample/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /BlazorWasmSample/BlazorWasmSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /BlazorWasmSample/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 |

Home

4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /BlazorWasmSample/Pages/LoggedOut.razor: -------------------------------------------------------------------------------- 1 | @page "/loggedout" 2 | 3 |

Logged Out

4 | Home -------------------------------------------------------------------------------- /BlazorWasmSample/Pages/Subpage.razor: -------------------------------------------------------------------------------- 1 | @page "/subpage" 2 | 3 |

Subpage

4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /BlazorWasmSample/Program.cs: -------------------------------------------------------------------------------- 1 | using Blazorade.Msal.Configuration; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Net.Http; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace BlazorWasmSample 13 | { 14 | public class Program 15 | { 16 | public static async Task Main(string[] args) 17 | { 18 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 19 | builder.RootComponents.Add("#app"); 20 | 21 | builder.Services 22 | .AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }) 23 | .AddBlazoradeMsal((sp, o) => 24 | { 25 | var root = sp.GetService(); 26 | var config = root.GetSection("app"); 27 | config.Bind(o); 28 | 29 | o.PostLogoutUrl = "/loggedout"; 30 | o.InteractiveLoginMode = InteractiveLoginMode.Redirect; 31 | }) 32 | ; 33 | 34 | await builder.Build().RunAsync(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BlazorWasmSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52274", 7 | "sslPort": 44340 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "BlazorWasmSample": { 20 | "commandName": "Project", 21 | "dotnetRunMessages": "true", 22 | "launchBrowser": true, 23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BlazorWasmSample/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | @Body 5 |
6 | -------------------------------------------------------------------------------- /BlazorWasmSample/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | .main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | } 28 | 29 | .top-row a:first-child { 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | } 33 | 34 | @media (max-width: 640.98px) { 35 | .top-row:not(.auth) { 36 | display: none; 37 | } 38 | 39 | .top-row.auth { 40 | justify-content: space-between; 41 | } 42 | 43 | .top-row a, .top-row .btn-link { 44 | margin-left: 0; 45 | } 46 | } 47 | 48 | @media (min-width: 641px) { 49 | .page { 50 | flex-direction: row; 51 | } 52 | 53 | .sidebar { 54 | width: 250px; 55 | height: 100vh; 56 | position: sticky; 57 | top: 0; 58 | } 59 | 60 | .top-row { 61 | position: sticky; 62 | top: 0; 63 | z-index: 1; 64 | } 65 | 66 | .main > div { 67 | padding-left: 2rem !important; 68 | padding-right: 1.5rem !important; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /BlazorWasmSample/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using BlazorWasmSample 10 | @using BlazorWasmSample.Shared 11 | 12 | @using SharedComponentsSample 13 | 14 | @using Blazorade.Core 15 | @using Blazorade.Core.Components 16 | @using Blazorade.Core.Components.Server 17 | @using Blazorade.Core.Interop 18 | @using Blazorade.Msal 19 | @using Blazorade.Msal.Configuration 20 | @using Blazorade.Msal.Security 21 | @using Blazorade.Msal.Services 22 | -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | appsettings.json 3 | -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/appsettings.json.md: -------------------------------------------------------------------------------- 1 | # appsettings.json 2 | 3 | In order to be able to run this application locally, you need to create a `appsettings.json` file in the same folder with this documentation. This file is deliberately excluded from source control. The contents of that file must be: 4 | 5 | ``` JSON 6 | { 7 | "app": { 8 | "clientId": "", 9 | "tenantId": ".onmicrosoft.com" 10 | } 11 | } 12 | ``` 13 | 14 | - `clientId`: The Client ID or Application ID of your application as registered in Azure AD. 15 | - `tenantId`: The full tenant name or tenant ID (GUID) of the Azure AD tenant that your application is registered in. 16 | 17 | > Note that you can structure your configuration file however you want, as long as you handle it properly in your startup configuration. In Blazor WebAssembly applications this is typically done in [`Program.cs`](../Program.cs). -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | a, .btn-link { 8 | color: #0366d6; 9 | } 10 | 11 | .btn-primary { 12 | color: #fff; 13 | background-color: #1b6ec2; 14 | border-color: #1861ac; 15 | } 16 | 17 | .content { 18 | padding-top: 1.1rem; 19 | } 20 | 21 | .valid.modified:not([type=checkbox]) { 22 | outline: 1px solid #26b050; 23 | } 24 | 25 | .invalid { 26 | outline: 1px solid red; 27 | } 28 | 29 | .validation-message { 30 | color: red; 31 | } 32 | 33 | #blazor-error-ui { 34 | background: lightyellow; 35 | bottom: 0; 36 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 37 | display: none; 38 | left: 0; 39 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 40 | position: fixed; 41 | width: 100%; 42 | z-index: 1000; 43 | } 44 | 45 | #blazor-error-ui .dismiss { 46 | cursor: pointer; 47 | position: absolute; 48 | right: 0.75rem; 49 | top: 0.5rem; 50 | } 51 | -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/BlazorWasmSample/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/BlazorWasmSample/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/BlazorWasmSample/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/BlazorWasmSample/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/BlazorWasmSample/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlazorWasmSample/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BlazorWasmSample 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Loading...
16 | 17 |
18 | An unhandled error has occurred. 19 | Reload 20 | 🗙 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Blazorade-MSAL.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32210.238 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazorade.Msal", "Blazorade.Msal\Blazorade.Msal.csproj", "{765AB143-8B42-45D1-B98B-DBAD2DDC400E}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test Apps", "Test Apps", "{DFBDDB47-E181-4F5F-9292-84F6B869D856}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{03D1A783-7EF5-4AA5-ADAC-F11BEFAAA22D}" 11 | ProjectSection(SolutionItems) = preProject 12 | .gitignore = .gitignore 13 | .gitmodules = .gitmodules 14 | LICENSE = LICENSE 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MsalTestConsole", "MsalTestConsole\MsalTestConsole.csproj", "{F83AD5AF-2851-44C8-B9EF-C0E0555F4665}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{27D5BEFB-F2D9-4A84-A61F-E2ADA4F1B6E9}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorServerSample", "BlazorServerSample\BlazorServerSample.csproj", "{C104334F-D15A-4DB7-8A5C-CA5E704B92B5}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWasmSample", "BlazorWasmSample\BlazorWasmSample.csproj", "{79286AAB-E3AF-4647-8EA6-B680DEE68DB6}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedComponentsSample", "SharedComponentsSample\SharedComponentsSample.csproj", "{8616164C-935F-4118-848C-4592FDA21F9D}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphClient", "GraphClient\GraphClient.csproj", "{B70A430C-BDAD-4B20-A435-9C9A7F4C8207}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Debug|x64 = Debug|x64 34 | Debug|x86 = Debug|x86 35 | Release|Any CPU = Release|Any CPU 36 | Release|x64 = Release|x64 37 | Release|x86 = Release|x86 38 | EndGlobalSection 39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 40 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Debug|x64.Build.0 = Debug|Any CPU 44 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Debug|x86.ActiveCfg = Debug|Any CPU 45 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Debug|x86.Build.0 = Debug|Any CPU 46 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Release|x64.ActiveCfg = Release|Any CPU 49 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Release|x64.Build.0 = Release|Any CPU 50 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Release|x86.ActiveCfg = Release|Any CPU 51 | {765AB143-8B42-45D1-B98B-DBAD2DDC400E}.Release|x86.Build.0 = Release|Any CPU 52 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Debug|x64.ActiveCfg = Debug|Any CPU 55 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Debug|x64.Build.0 = Debug|Any CPU 56 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Debug|x86.ActiveCfg = Debug|Any CPU 57 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Debug|x86.Build.0 = Debug|Any CPU 58 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Release|x64.ActiveCfg = Release|Any CPU 61 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Release|x64.Build.0 = Release|Any CPU 62 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Release|x86.ActiveCfg = Release|Any CPU 63 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665}.Release|x86.Build.0 = Release|Any CPU 64 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Debug|x64.ActiveCfg = Debug|Any CPU 67 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Debug|x64.Build.0 = Debug|Any CPU 68 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Debug|x86.ActiveCfg = Debug|Any CPU 69 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Debug|x86.Build.0 = Debug|Any CPU 70 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Release|x64.ActiveCfg = Release|Any CPU 73 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Release|x64.Build.0 = Release|Any CPU 74 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Release|x86.ActiveCfg = Release|Any CPU 75 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5}.Release|x86.Build.0 = Release|Any CPU 76 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Debug|x64.ActiveCfg = Debug|Any CPU 79 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Debug|x64.Build.0 = Debug|Any CPU 80 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Debug|x86.ActiveCfg = Debug|Any CPU 81 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Debug|x86.Build.0 = Debug|Any CPU 82 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Release|x64.ActiveCfg = Release|Any CPU 85 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Release|x64.Build.0 = Release|Any CPU 86 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Release|x86.ActiveCfg = Release|Any CPU 87 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6}.Release|x86.Build.0 = Release|Any CPU 88 | {8616164C-935F-4118-848C-4592FDA21F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {8616164C-935F-4118-848C-4592FDA21F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {8616164C-935F-4118-848C-4592FDA21F9D}.Debug|x64.ActiveCfg = Debug|Any CPU 91 | {8616164C-935F-4118-848C-4592FDA21F9D}.Debug|x64.Build.0 = Debug|Any CPU 92 | {8616164C-935F-4118-848C-4592FDA21F9D}.Debug|x86.ActiveCfg = Debug|Any CPU 93 | {8616164C-935F-4118-848C-4592FDA21F9D}.Debug|x86.Build.0 = Debug|Any CPU 94 | {8616164C-935F-4118-848C-4592FDA21F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {8616164C-935F-4118-848C-4592FDA21F9D}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {8616164C-935F-4118-848C-4592FDA21F9D}.Release|x64.ActiveCfg = Release|Any CPU 97 | {8616164C-935F-4118-848C-4592FDA21F9D}.Release|x64.Build.0 = Release|Any CPU 98 | {8616164C-935F-4118-848C-4592FDA21F9D}.Release|x86.ActiveCfg = Release|Any CPU 99 | {8616164C-935F-4118-848C-4592FDA21F9D}.Release|x86.Build.0 = Release|Any CPU 100 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 101 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Debug|Any CPU.Build.0 = Debug|Any CPU 102 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Debug|x64.ActiveCfg = Debug|Any CPU 103 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Debug|x64.Build.0 = Debug|Any CPU 104 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Debug|x86.ActiveCfg = Debug|Any CPU 105 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Debug|x86.Build.0 = Debug|Any CPU 106 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Release|Any CPU.ActiveCfg = Release|Any CPU 107 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Release|Any CPU.Build.0 = Release|Any CPU 108 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Release|x64.ActiveCfg = Release|Any CPU 109 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Release|x64.Build.0 = Release|Any CPU 110 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Release|x86.ActiveCfg = Release|Any CPU 111 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207}.Release|x86.Build.0 = Release|Any CPU 112 | EndGlobalSection 113 | GlobalSection(SolutionProperties) = preSolution 114 | HideSolutionNode = FALSE 115 | EndGlobalSection 116 | GlobalSection(NestedProjects) = preSolution 117 | {F83AD5AF-2851-44C8-B9EF-C0E0555F4665} = {DFBDDB47-E181-4F5F-9292-84F6B869D856} 118 | {C104334F-D15A-4DB7-8A5C-CA5E704B92B5} = {27D5BEFB-F2D9-4A84-A61F-E2ADA4F1B6E9} 119 | {79286AAB-E3AF-4647-8EA6-B680DEE68DB6} = {27D5BEFB-F2D9-4A84-A61F-E2ADA4F1B6E9} 120 | {8616164C-935F-4118-848C-4592FDA21F9D} = {27D5BEFB-F2D9-4A84-A61F-E2ADA4F1B6E9} 121 | {B70A430C-BDAD-4B20-A435-9C9A7F4C8207} = {27D5BEFB-F2D9-4A84-A61F-E2ADA4F1B6E9} 122 | EndGlobalSection 123 | GlobalSection(ExtensibilityGlobals) = postSolution 124 | SolutionGuid = {7A02B975-9E79-4692-8B2F-B5B4063C67FF} 125 | EndGlobalSection 126 | EndGlobal 127 | -------------------------------------------------------------------------------- /Blazorade.Msal/Blazorade-Icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/Blazorade.Msal/Blazorade-Icon-128.png -------------------------------------------------------------------------------- /Blazorade.Msal/Blazorade.Msal.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 2.2.0 6 | Mika Berglund 7 | Blazorade 8 | Blazorade MSAL 9 | Provides easy to use authentication and token acquisition for Blazor applications with the help of Microsoft Authentication Library. Supports both Blazor Server and Blazor WebAssembly applications. 10 | MIT 11 | https://github.com/Blazorade/Blazorade-MSAL/wiki 12 | https://github.com/Blazorade/Blazorade-MSAL 13 | GitHub 14 | Blazor, Blazor Server, Blazor WASM, MSAL, Authentication 15 | Blazorade-Icon-128.png 16 | Copyright (c) Mika Berglund 2022 17 | readme.md 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | True 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | True 44 | 45 | 46 | 47 | True 48 | \ 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Blazorade.Msal/Components/LoginRedirectHandler.razor: -------------------------------------------------------------------------------- 1 | @inherits BlazoradeComponentBase 2 | @inject BlazoradeMsalService msalService 3 | -------------------------------------------------------------------------------- /Blazorade.Msal/Components/LoginRedirectHandler.razor.cs: -------------------------------------------------------------------------------- 1 | using Blazorade.Msal.Security; 2 | using Microsoft.AspNetCore.Components; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Blazorade.Msal.Components 10 | { 11 | /// 12 | /// Use this component on your login page if you are using redirect login in your application. 13 | /// 14 | /// 15 | /// 16 | /// The component needs to be added to the page that is configured as the redirect URI in your application, 17 | /// both in Azure AD and in the startup configuration for your application. 18 | /// 19 | /// 20 | /// The component will process the redirect when a user is redirected back from signing in. Depending on the outcome of the login process, 21 | /// either or event is triggered. 22 | /// 23 | /// 24 | partial class LoginRedirectHandler 25 | { 26 | 27 | /// 28 | /// The event that is triggered when the component has successfully processed a login redirect request. The argument with the event 29 | /// contains the result of the authentication. 30 | /// 31 | [Parameter] 32 | public EventCallback OnLoginSucceeded { get; set; } 33 | 34 | /// 35 | /// The event that is triggered when the login has failed. The argument with the event contains the exception. 36 | /// 37 | [Parameter] 38 | public EventCallback OnLoginFailed { get; set; } 39 | 40 | 41 | 42 | protected async override Task OnAfterRenderAsync(bool firstRender) 43 | { 44 | await base.OnAfterRenderAsync(firstRender); 45 | 46 | if (firstRender) 47 | { 48 | AuthenticationResult token = null; 49 | 50 | try 51 | { 52 | token = await this.msalService.HandleRedirectPromiseAsync(); 53 | if (null != token) 54 | { 55 | await this.OnLoginSucceeded.InvokeAsync(token); 56 | } 57 | else 58 | { 59 | await this.OnLoginFailed.InvokeAsync(); 60 | } 61 | } 62 | catch (Exception ex) 63 | { 64 | await this.OnLoginFailed.InvokeAsync(ex); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Blazorade.Msal/Configuration/AuthorityType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Blazorade.Msal.Configuration 8 | { 9 | /// 10 | /// Defines different authority types that Blazorade MSAL support. 11 | /// 12 | public enum AuthorityType 13 | { 14 | /// 15 | /// Azure AD. Only the and are required 16 | /// to be configured. 17 | /// 18 | /// 19 | /// 20 | /// This is the default authority type used by the configuration options class. 21 | /// 22 | /// 23 | /// You can override this default behaviour by setting to a full URL in the form 24 | /// of https://login.microsoftonline.com/<tenant ID> 25 | /// 26 | /// 27 | AAD, 28 | 29 | /// 30 | /// Azure AD B2C. With this option, you need to specify at least the following options: 31 | /// , and . 32 | /// 33 | /// 34 | /// 35 | /// You can override the default behaviour by setting the to a full URL in the form 36 | /// of https://<Tenant Name>.b2clogin.com/<Tenant Name>.onmicrosoft.com/<Policy ID> 37 | /// 38 | /// 39 | AADB2C, 40 | 41 | /// 42 | /// A generic authority that supports authentication with Open ID Connect. 43 | /// 44 | /// 45 | /// When you use this options, you must specify the full URL for the configuration option. 46 | /// 47 | OidcGeneric 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Blazorade.Msal/Configuration/BlazoradeMsalOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Blazorade.Msal.Configuration 8 | { 9 | /// 10 | /// Contains options for customizing how your application uses MSAL. 11 | /// 12 | public class BlazoradeMsalOptions 13 | { 14 | 15 | /// 16 | /// Creates a new instance of the class and sets the defaults. 17 | /// 18 | public BlazoradeMsalOptions() 19 | { 20 | this.MsalVersion = "2.11.0"; 21 | this.InteractiveLoginMode = InteractiveLoginMode.Redirect; 22 | this.DefaultScopes = new string[] { "openid", "profile" }; 23 | this.TokenCacheScope = TokenCacheScope.Session; 24 | 25 | this.AuthorityType = AuthorityType.AAD; 26 | } 27 | 28 | /// 29 | /// The client ID representing your application. 30 | /// 31 | public string ClientId { get; set; } 32 | 33 | /// 34 | /// The name or ID of the endpoint specified by the configuration options. 35 | /// 36 | /// 37 | /// For instance with Azure AD B2C, this is the ID of the policy you want to use for users when signing in. 38 | /// 39 | public string EndpointId { get; set; } 40 | 41 | /// 42 | /// The tenant ID in which the application is registered. 43 | /// 44 | /// 45 | /// The tenant ID can either be a Guid, the default domain, i.e. [your tenant].onmicrosoft.com, 46 | /// or a vanity domain like yourcompany.com. If your application is a multi-tenant application, 47 | /// you can also set the tenant to common, organizations or consumers. 48 | /// 49 | public string TenantId { get; set; } 50 | 51 | /// 52 | /// The full authority URI representing the Azure AD tenant that will take care of authenticating your users. If this 53 | /// property is set, is ignored. 54 | /// 55 | /// 56 | /// 57 | /// The TenantId option must always be set to a value that can be resolved by Azure AD. This means that it has 58 | /// to be formatted as one of the following. 59 | /// 60 | /// 61 | /// Default domain name 62 | /// 63 | /// The default domain name is formatted as <tenant name>.onmicrosoft.com. With Azure AD B2C, this 64 | /// is the only supported option. 65 | /// 66 | /// 67 | /// 68 | /// Tenant GUID 69 | /// You can specify the tenant as the directory GUID. 70 | /// 71 | /// 72 | /// Vanity domain 73 | /// 74 | /// You can also use your vanity domain to define your tenant. 75 | /// 76 | /// 77 | /// 78 | /// 79 | /// 80 | /// The Authority property allows you to use both Azure AD and Azure AD B2C. Depending on which directory 81 | /// you are using, the value is constructed a bit differently. 82 | /// 83 | /// 84 | /// 85 | /// Azure AD 86 | /// https://login.microsoftonline.com/{tenant-name}.onmicrosoft.com 87 | /// 88 | /// 89 | /// Azure AD B2C 90 | /// https://{tenant-name}.b2clogin.com/{tenant-name}.onmicrosoft.com/{policy-name} 91 | /// 92 | /// 93 | /// 94 | public string Authority { get; set; } 95 | 96 | /// 97 | /// The type of authority to use. 98 | /// 99 | /// 100 | /// 101 | /// Using this configuration option helps you in formatting the URL to use when logging in. 102 | /// 103 | /// 104 | /// You can always override this setting by explicitly setting the URL in your 105 | /// configuration options. 106 | /// 107 | /// 108 | /// The default value is . 109 | /// 110 | /// 111 | public AuthorityType AuthorityType { get; set; } 112 | 113 | /// 114 | /// The MSAL Browser version to use. 115 | /// 116 | /// 117 | /// Defaults to 2.22.0. For more information see https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser. 118 | /// 119 | public string MsalVersion { get; set; } = "2.22.0"; 120 | 121 | /// 122 | /// Defines how interactive login is handled. 123 | /// 124 | /// 125 | /// Defaults to . 126 | /// 127 | public InteractiveLoginMode InteractiveLoginMode { get; set; } = InteractiveLoginMode.Redirect; 128 | 129 | /// 130 | /// The default scopes to acquire if none are specified when acquiring tokens. 131 | /// 132 | /// 133 | /// Defaults to an array with openid and profile. 134 | /// 135 | public IEnumerable DefaultScopes { get; set; } = new string[] { "openid", "profile" }; 136 | 137 | /// 138 | /// The redirect URI for your application. 139 | /// 140 | /// 141 | /// 142 | /// This is the URL where your users are redirected back after interactive login if 143 | /// is set to . 144 | /// 145 | /// 146 | /// Can be set to an absolute or relative URI. 147 | /// 148 | /// 149 | public string RedirectUrl { get; set; } 150 | 151 | /// 152 | /// The URI that the users are redirected to after logging out of your application. 153 | /// 154 | /// 155 | /// 156 | /// Can be set to an absolute or relative URI. 157 | /// 158 | /// 159 | public string PostLogoutUrl { get; set; } 160 | 161 | /// 162 | /// Defines how tokens are cached. 163 | /// 164 | /// 165 | /// Defaults to . 166 | /// 167 | public TokenCacheScope TokenCacheScope { get; set; } = TokenCacheScope.Session; 168 | 169 | 170 | 171 | internal string GetAuthority() 172 | { 173 | return this.Authority?.Length > 0 174 | ? this.Authority 175 | : this.BuildAuthority(); 176 | } 177 | 178 | 179 | private string BuildAuthority() 180 | { 181 | string url = null; 182 | 183 | if(this.AuthorityType == AuthorityType.AAD) 184 | { 185 | url = $"https://login.microsoftonline.com/{ this.TenantId ?? "common" }"; 186 | } 187 | else if (this.AuthorityType == AuthorityType.AADB2C) 188 | { 189 | var segments = this.TenantId.Split('.'); 190 | if(segments.Length < 3) 191 | { 192 | throw new Exception("The TenantId configuration option must be specified as '.onmicrosoft.com' for Azure AD B2C."); 193 | } 194 | if(string.IsNullOrEmpty(this.EndpointId)) 195 | { 196 | throw new Exception("You must set the EndpointId configuration option to the ID of the policy you want to use to log in with in Azure AD B2C."); 197 | } 198 | 199 | url = $"https://{segments[0]}.b2clogin.com/{this.TenantId}/{this.EndpointId}"; 200 | } 201 | else 202 | { 203 | throw new Exception($"Cannot build the Authority URL for this kind of authority type: '{this.AuthorityType}'."); 204 | } 205 | 206 | return url; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Blazorade.Msal/Configuration/InteractiveLoginMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Blazorade.Msal.Configuration 8 | { 9 | /// 10 | /// Defines different ways of doing interactive login. 11 | /// 12 | public enum InteractiveLoginMode 13 | { 14 | /// 15 | /// Uses the default popup provided by MSAL for interactive login. This usually works in web applications. 16 | /// 17 | Popup, 18 | 19 | /// 20 | /// Handles login by using redirection. 21 | /// 22 | Redirect, 23 | 24 | /// 25 | /// Interactive login is disabled. 26 | /// 27 | Disabled 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Blazorade.Msal/Configuration/ServiceCollectionExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using Blazorade.Msal.Configuration; 2 | using Blazorade.Msal.Services; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Extensions.DependencyInjection 10 | { 11 | /// 12 | /// Extension methods for working with a implementation. 13 | /// 14 | public static class ServiceCollectionExtensionMethods 15 | { 16 | 17 | public static IServiceCollection AddBlazoradeMsal(this IServiceCollection services) 18 | { 19 | return services 20 | .AddScoped() 21 | .AddScoped() 22 | ; 23 | } 24 | 25 | public static IServiceCollection AddBlazoradeMsal(this IServiceCollection services, Action config) 26 | { 27 | return services 28 | .AddBlazoradeMsal() 29 | .AddBlazoradeMsal((sp, o) => 30 | { 31 | config?.Invoke(o); 32 | }); 33 | } 34 | 35 | public static IServiceCollection AddBlazoradeMsal(this IServiceCollection services, Action config) 36 | { 37 | return services 38 | .AddBlazoradeMsal() 39 | .AddSingleton((p) => 40 | { 41 | var options = new BlazoradeMsalOptions(); 42 | config?.Invoke(p, options); 43 | return options; 44 | }); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Blazorade.Msal/Configuration/TokenCacheScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Blazorade.Msal.Configuration 8 | { 9 | /// 10 | /// Specifies how tokens are cached. 11 | /// 12 | public enum TokenCacheScope 13 | { 14 | /// 15 | /// Tokens are cached for a single browser session. 16 | /// 17 | /// 18 | /// May result in that tokens are not shared across multiple browser tabs. 19 | /// 20 | Session, 21 | 22 | /// 23 | /// Tokens are persisted across browser sessions. 24 | /// 25 | Persistent 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Blazorade.Msal/InternalExtensions.cs: -------------------------------------------------------------------------------- 1 | using Blazorade.Msal.Security; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Blazorade.Msal 9 | { 10 | internal static class InternalExtensions 11 | { 12 | 13 | public static string ToStringValue(this LoginPrompt? prompt) 14 | { 15 | switch (prompt) 16 | { 17 | case LoginPrompt.Consent: 18 | return "consent"; 19 | 20 | case LoginPrompt.Login: 21 | return "login"; 22 | 23 | case LoginPrompt.None: 24 | return "none"; 25 | 26 | case LoginPrompt.SelectAccount: 27 | return "select_account"; 28 | 29 | default: 30 | return null; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Blazorade.Msal/Security/Account.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Blazorade.Msal.Security 8 | { 9 | public sealed class Account 10 | { 11 | public string UserName { get; set; } 12 | 13 | public string Name { get; set; } 14 | 15 | public Dictionary IdTokenClaims { get; set; } 16 | 17 | public string Sid { get; set; } 18 | 19 | public string Environment { get; set; } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Blazorade.Msal/Security/AuthenticationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Blazorade.Msal.Security 8 | { 9 | public sealed class AuthenticationResult 10 | { 11 | public AuthenticationResult() 12 | { 13 | this.IdTokenClaims = new Dictionary(); 14 | } 15 | 16 | public string Authority { get; set; } 17 | 18 | public string UniqueId { get; set; } 19 | 20 | public string TenantId { get; set; } 21 | 22 | public string TokenType { get; set; } 23 | 24 | public string IdToken { get; set; } 25 | 26 | public Dictionary IdTokenClaims { get; set; } 27 | 28 | public string AccessToken { get; set; } 29 | 30 | public List Scopes { get; set; } 31 | 32 | public DateTimeOffset? ExpiresOn { get; set; } 33 | 34 | public Account Account { get; set; } 35 | 36 | public bool FromCache { get; set; } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Blazorade.Msal/Security/LoginPrompt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Blazorade.Msal.Security 8 | { 9 | /// 10 | /// Defines different prompt behaviour for interactive login. 11 | /// 12 | public enum LoginPrompt 13 | { 14 | /// 15 | /// Will force the user to enter their credentials on that request, negating single-sign on. 16 | /// 17 | Login, 18 | 19 | /// 20 | /// Will the trigger the OAuth consent dialog after the user signs in, asking the user to grant permissions to the app. 21 | /// 22 | Consent, 23 | 24 | /// 25 | /// Will interrupt single sign-on providing account selection experience listing all the accounts in session or any remembered accounts or an option to choose to use a different account. 26 | /// 27 | SelectAccount, 28 | 29 | /// 30 | /// Will ensure that the user isn't presented with any interactive prompt. if request can't be completed via single-sign on, the login process will fail. 31 | /// 32 | None 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Blazorade.Msal/Services/BlazoradeMsalService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using Blazorade.Core.Interop; 8 | using Blazorade.Msal.Configuration; 9 | using Blazorade.Msal.Security; 10 | using Microsoft.AspNetCore.Components; 11 | using Microsoft.JSInterop; 12 | using Microsoft.Extensions.DependencyInjection; 13 | 14 | namespace Blazorade.Msal.Services 15 | { 16 | /// 17 | /// A service class implementation for working with tokens acquired by Microsoft Authentication Library. 18 | /// 19 | /// 20 | /// An instance of this class is added to the services collection using one of the 21 | /// methods in your application's 22 | /// startup class. It can then be injected into your application. 23 | /// 24 | public class BlazoradeMsalService 25 | { 26 | /// 27 | /// Creates an instance of the service class. 28 | /// 29 | public BlazoradeMsalService(BlazoradeMsalOptions options, IJSRuntime jsRuntime, NavigationManager navMan) 30 | { 31 | this.Options = options ?? throw new ArgumentNullException(nameof(options)); 32 | this.JSRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); 33 | this.NavMan = navMan ?? throw new ArgumentNullException(nameof(navMan)); 34 | } 35 | 36 | private BlazoradeMsalOptions Options; 37 | private IJSRuntime JSRuntime; 38 | private NavigationManager NavMan; 39 | 40 | private const int DefaultTimeout = 5000; 41 | private const int DefaultInteractiveTimeout = 60000; 42 | 43 | /// 44 | /// Acquires a token with the given parameters. 45 | /// 46 | /// 47 | /// This method first tries to acquire the token silently using the method. 48 | /// If that failes, then the interactive option is used using the method. 49 | /// 50 | /// 51 | /// The username to acquire the token for. If the token cannot be acquired silently, this value will also be 52 | /// passed to the interactive method to minimize the amount of information the user has to enter. 53 | /// 54 | /// 55 | /// The scopes that must be included in the acquired token. If the user has not consented to one or more of these tokens, 56 | /// the user will be taken to the interactive mode. If not specified, the default configured scopes will be used. 57 | /// 58 | /// 59 | /// Specifies whether to fall back to the default login hint. The default login hint is the login hint that was previously used to 60 | /// acquire a token. 61 | /// 62 | /// 63 | /// Defines a prompt behaviour, in case this method falls back to interactive login. 64 | /// 65 | public async Task AcquireTokenAsync(string loginHint = null, IEnumerable scopes = null, bool fallbackToDefaultLoginHint = false, LoginPrompt? prompt = null) 66 | { 67 | return await this.AcquireTokenAsync(new TokenAcquisitionRequest 68 | { 69 | LoginHint = loginHint, 70 | Scopes = scopes, 71 | FallbackToDefaultLoginHint = fallbackToDefaultLoginHint, 72 | Prompt = prompt 73 | }); 74 | } 75 | 76 | /// 77 | /// Acquires a token with the given parameters. 78 | /// 79 | /// 80 | /// This method first tries to acquire the token silently using the method. 81 | /// If that failes, then the interactive option is used using the method. 82 | /// 83 | /// Defines how to request for a token. 84 | public virtual async Task AcquireTokenAsync(TokenAcquisitionRequest request) 85 | { 86 | AuthenticationResult result = null; 87 | 88 | // If prompt is specified, and it is something else than None, then we must skip the silent acquisition. 89 | 90 | if (!request.Prompt.HasValue || request.Prompt == LoginPrompt.None) 91 | { 92 | try 93 | { 94 | result = await this.AcquireTokenSilentAsync(request); 95 | } 96 | // Deliberately just swallowing any error, since if we cannot get a token this way, then we use another fallback method. 97 | catch (FailureCallbackException) { } 98 | } 99 | 100 | if (null == result) 101 | { 102 | try 103 | { 104 | result = await this.AcquireTokenInteractiveAsync(request); 105 | } 106 | catch (FailureCallbackException) { } 107 | } 108 | 109 | return result; 110 | } 111 | 112 | /// 113 | /// Acquires a token interactively asking the user for input. Depending on your application's configuration, the token is acquired either using 114 | /// a popup dialog, or by redirecting the user to the login. 115 | /// 116 | /// A login hint to use, i.e. the username, if known. 117 | /// 118 | /// The scopes that must be included in the acquired token. If not specified, the default configured scopes will be used. 119 | /// 120 | /// The prompt behaviour to use. If not specified, no specific prompt behaviour is used. It will be determined by what is needed. 121 | public async Task AcquireTokenInteractiveAsync(string loginHint = null, IEnumerable scopes = null, LoginPrompt? prompt = null) 122 | { 123 | return await this.AcquireTokenInteractiveAsync(new TokenAcquisitionRequest 124 | { 125 | LoginHint = loginHint, 126 | Scopes = scopes, 127 | Prompt = prompt 128 | }); 129 | } 130 | 131 | /// 132 | /// Acquires a token interactively asking the user for input. Depending on your application's configuration, the token is acquired either using 133 | /// a popup dialog, or by redirecting the user to the login. 134 | /// 135 | /// Defines how to request for a token. 136 | public virtual async Task AcquireTokenInteractiveAsync(TokenAcquisitionRequest request) 137 | { 138 | if (this.Options.InteractiveLoginMode == InteractiveLoginMode.Popup) 139 | { 140 | return await this.AcquireTokenPopupAsync(request); 141 | } 142 | else if (this.Options.InteractiveLoginMode == InteractiveLoginMode.Redirect) 143 | { 144 | await this.AcquireTokenRedirectAsync(request); 145 | } 146 | 147 | return null; 148 | } 149 | 150 | /// 151 | /// Acquires a token with a popup dialog. 152 | /// 153 | /// A login hint to use, i.e. the username, if known. 154 | /// 155 | /// The scopes that must be included in the acquired token. If not specified, the default configured scopes will be used. 156 | /// 157 | /// The prompt behaviour to use. If not specified, no specific prompt behaviour is used. It will be determined by what is needed. 158 | /// 159 | /// It is recommended to use the method, which will determine the interaction mode from the application's configuration. 160 | /// 161 | public async Task AcquireTokenPopupAsync(string loginHint = null, IEnumerable scopes = null, LoginPrompt? prompt = null) 162 | { 163 | return await this.AcquireTokenPopupAsync(new TokenAcquisitionRequest 164 | { 165 | LoginHint = loginHint, 166 | Scopes = scopes, 167 | Prompt = prompt 168 | }); 169 | } 170 | 171 | /// 172 | /// Acquires a token with a popup dialog. 173 | /// 174 | /// Defines how to request for a token. 175 | public virtual async Task AcquireTokenPopupAsync(TokenAcquisitionRequest request) 176 | { 177 | if (null == request) throw new ArgumentNullException(nameof(request)); 178 | 179 | var module = await this.GetBlazoradeModuleAsync(); 180 | var data = this.CreateMsalData(loginHint: request?.LoginHint, scopes: request?.Scopes, prompt: request?.Prompt); 181 | 182 | AuthenticationResult result = null; 183 | using (var handler = new DotNetInstanceCallbackHandler(module, "acquireTokenPopup", data)) 184 | { 185 | result = await handler.GetResultAsync(timeout: request.Timeout ?? DefaultInteractiveTimeout); 186 | } 187 | return result; 188 | } 189 | 190 | /// 191 | /// Acquires a token by redirecting the user to the identity provider. 192 | /// 193 | /// A login hint to use, i.e. the username, if known. 194 | /// 195 | /// The scopes that must be included in the acquired token. If not specified, the default configured scopes will be used. 196 | /// 197 | /// The prompt behaviour to use. If not specified, no specific prompt behaviour is used. It will be determined by what is needed. 198 | /// 199 | /// 200 | /// Calling this method will redirect the user to login. When the user returns to your login page, you need to process the result using the 201 | /// method, which will then returns the token. 202 | /// 203 | /// 204 | /// It is recommended to use the method, which will determine the interaction mode from the application's configuration. 205 | /// 206 | /// 207 | public async Task AcquireTokenRedirectAsync(string loginHint = null, IEnumerable scopes = null, LoginPrompt? prompt = null) 208 | { 209 | await this.AcquireTokenRedirectAsync(new TokenAcquisitionRequest 210 | { 211 | LoginHint = loginHint, 212 | Scopes = scopes, 213 | Prompt = prompt 214 | }); 215 | } 216 | 217 | /// 218 | /// Acquires a token by redirecting the user to the identity provider. 219 | /// 220 | /// Defines how to request for a token. 221 | public virtual async Task AcquireTokenRedirectAsync(TokenAcquisitionRequest request) 222 | { 223 | var module = await this.GetBlazoradeModuleAsync(); 224 | var data = this.CreateMsalData(loginHint: request?.LoginHint, scopes: request?.Scopes, prompt: request?.Prompt); 225 | 226 | using (var handler = new DotNetInstanceCallbackHandler(module, "acquireTokenRedirect", data)) 227 | { 228 | await handler.GetResultAsync(timeout: request.Timeout ?? DefaultInteractiveTimeout); 229 | } 230 | } 231 | 232 | /// 233 | /// Acquires a token silently without user interaction. 234 | /// 235 | /// 236 | /// 237 | /// If the application is configured to use , then this method will also 238 | /// attempt to call in case the method is invoked on a page that the user 239 | /// was redirected to back from the identity provider. 240 | /// 241 | /// 242 | /// No interactive login is ever invoked by this method. 243 | /// 244 | /// 245 | /// A login hint to use, i.e. the username, if known. 246 | /// 247 | /// The scopes that must be included in the acquired token. If the user has not consented to one or more of these tokens, 248 | /// the user will be taken to the interactive mode. If not specified, the default configured scopes will be used. 249 | /// 250 | /// 251 | /// Specifies whether to fall back to the default login hint. The default login hint is the login hint that was previously used to 252 | /// acquire a token. 253 | /// 254 | public async Task AcquireTokenSilentAsync(string loginHint = null, IEnumerable scopes = null, bool fallbackToDefaultLoginHint = false) 255 | { 256 | return await this.AcquireTokenSilentAsync(new TokenAcquisitionRequest 257 | { 258 | LoginHint = loginHint, 259 | Scopes = scopes, 260 | FallbackToDefaultLoginHint = fallbackToDefaultLoginHint 261 | }); 262 | } 263 | 264 | /// 265 | /// Acquires a token silently without user interaction. 266 | /// 267 | /// Defines how to request for a token. 268 | public virtual async Task AcquireTokenSilentAsync(TokenAcquisitionRequest request) 269 | { 270 | AuthenticationResult result = null; 271 | var module = await this.GetBlazoradeModuleAsync(); 272 | 273 | if (this.Options.InteractiveLoginMode == InteractiveLoginMode.Redirect) 274 | { 275 | try 276 | { 277 | result = await this.HandleRedirectPromiseAsync(); 278 | } 279 | catch { } 280 | } 281 | 282 | if (null == result) 283 | { 284 | var data = this.CreateMsalData(loginHint: request?.LoginHint, scopes: request?.Scopes, fallbackToDefaultLoginHint: request?.FallbackToDefaultLoginHint); 285 | using (var handler = new DotNetInstanceCallbackHandler(module, "acquireTokenSilent", data)) 286 | { 287 | result = await handler.GetResultAsync(timeout: request.Timeout ?? DefaultTimeout); 288 | } 289 | } 290 | 291 | return result; 292 | } 293 | 294 | /// 295 | /// Returns the default login hint for the current user. The default login hint is the login hint that was previously used to acquire a token. 296 | /// 297 | public virtual async Task GetDefaultLoginHintAsync() 298 | { 299 | var module = await this.GetBlazoradeModuleAsync(); 300 | var data = this.CreateMsalData(); 301 | 302 | string result = null; 303 | using (var handler = new DotNetInstanceCallbackHandler(module, "getDefaultLoginHint", data)) 304 | { 305 | result = await handler.GetResultAsync(timeout: DefaultTimeout); 306 | } 307 | 308 | return result; 309 | } 310 | 311 | /// 312 | /// Assumes that the current request is a redirect back from the identity provider, and attempt to process information sent back to the 313 | /// application from the identity provider. 314 | /// 315 | /// 316 | /// Returns null if the current request is not a redirect back from the identity provider. 317 | /// 318 | /// The exception that is thrown if the current request is a redirect back from login, but the redirect specifies an error with the login. 319 | public virtual async Task HandleRedirectPromiseAsync() 320 | { 321 | var module = await this.GetBlazoradeModuleAsync(); 322 | var data = this.CreateMsalData(navigateToLoginRequestUrl: false); 323 | 324 | AuthenticationResult result = null; 325 | using (var handler = new DotNetInstanceCallbackHandler(module, "handleRedirectPromise", data)) 326 | { 327 | result = await handler.GetResultAsync(timeout: DefaultTimeout); 328 | } 329 | return result; 330 | } 331 | 332 | /// 333 | /// Checks whether the application has a valid token for the scenario specified in . 334 | /// 335 | /// Defines how to check for a token. 336 | /// 337 | /// Returns true if there is a valid token available. 338 | /// 339 | /// 340 | /// This method calls the method and catches any exceptions. 341 | /// If there are no exceptions and the result of that method call contains both an access token and an ID token, this 342 | /// method returns true. Otherwise the method returns false. 343 | /// 344 | /// The exception that is thrown if is null. 345 | public virtual async Task HasTokenAsync(TokenAcquisitionRequest request) 346 | { 347 | if (null == request) throw new ArgumentNullException(nameof(request)); 348 | 349 | try 350 | { 351 | request.FallbackToDefaultLoginHint = true; 352 | var result = await this.AcquireTokenSilentAsync(request); 353 | return result?.AccessToken?.Length > 0 && result?.IdToken?.Length > 0; 354 | } 355 | catch { } 356 | 357 | return false; 358 | } 359 | 360 | /// 361 | /// Performs a logout of the current user. 362 | /// 363 | public virtual async Task LogoutAsync() 364 | { 365 | var module = await this.GetBlazoradeModuleAsync(); 366 | var data = this.CreateMsalData(); 367 | 368 | using (var handler = new DotNetInstanceCallbackHandler(module, "logout", data)) 369 | { 370 | await handler.GetResultAsync(timeout: DefaultTimeout); 371 | } 372 | } 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | private Dictionary CreateMsalConfig(bool navigateToLoginRequestUrl = true) 381 | { 382 | 383 | #region auth 384 | 385 | var auth = new Dictionary 386 | { 387 | { "clientId", this.Options.ClientId }, 388 | { "authority", this.Options.GetAuthority() }, 389 | { "navigateToLoginRequestUrl", navigateToLoginRequestUrl } 390 | }; 391 | 392 | var redirectUri = this.CreateAbsoluteUri(this.Options.RedirectUrl); 393 | if (null != redirectUri) 394 | { 395 | auth["redirectUri"] = redirectUri; 396 | } 397 | 398 | var postLogoutRedirectUri = this.CreateAbsoluteUri(this.Options.PostLogoutUrl); 399 | if (null != postLogoutRedirectUri) 400 | { 401 | auth["postLogoutRedirectUri"] = postLogoutRedirectUri; 402 | } 403 | 404 | #endregion 405 | 406 | #region cache 407 | 408 | var cache = new Dictionary 409 | { 410 | { "cacheLocation", "sessionStorage" } 411 | }; 412 | if(this.Options.TokenCacheScope == TokenCacheScope.Persistent) 413 | { 414 | cache["cacheLocation"] = "localStorage"; 415 | } 416 | 417 | #endregion 418 | 419 | var msalConfig = new Dictionary 420 | { 421 | { "auth", auth }, 422 | { "cache", cache } 423 | }; 424 | return msalConfig; 425 | } 426 | 427 | private Dictionary CreateMsalData(string loginHint = null, IEnumerable scopes = null, bool navigateToLoginRequestUrl = true, bool? fallbackToDefaultLoginHint = null, LoginPrompt? prompt = null) 428 | { 429 | var msalConfig = this.CreateMsalConfig(navigateToLoginRequestUrl); 430 | 431 | var data = new Dictionary 432 | { 433 | { "scopes", scopes?.Count() > 0 ? scopes : this.Options.DefaultScopes?.Count() > 0 ? this.Options.DefaultScopes : new string[] { ".default" } }, 434 | { "msalConfig", msalConfig }, 435 | }; 436 | 437 | if(loginHint?.Length > 0) 438 | { 439 | data["loginHint"] = loginHint; 440 | } 441 | 442 | if (fallbackToDefaultLoginHint.GetValueOrDefault()) 443 | { 444 | data["fallbackToDefaultLoginHint"] = true; 445 | } 446 | 447 | if (prompt.HasValue) 448 | { 449 | data["prompt"] = prompt.ToStringValue(); 450 | } 451 | return data; 452 | } 453 | 454 | private Uri CreateAbsoluteUri(string relativeOrAbsoluteUrl) 455 | { 456 | if(relativeOrAbsoluteUrl?.Length > 0) 457 | { 458 | var uri = new Uri(relativeOrAbsoluteUrl, UriKind.RelativeOrAbsolute); 459 | if (!uri.IsAbsoluteUri) 460 | { 461 | uri = this.NavMan.ToAbsoluteUri(uri.ToString()); 462 | } 463 | 464 | return uri; 465 | } 466 | return null; 467 | } 468 | 469 | private IJSObjectReference _BlazoradeModule; 470 | private async Task GetBlazoradeModuleAsync() 471 | { 472 | var msalModule = await this.GetMsalModuleAsync(); 473 | return _BlazoradeModule ??= await this.JSRuntime.InvokeAsync("import", "./_content/Blazorade.Msal/js/blazoradeMsal.js").AsTask(); 474 | } 475 | 476 | private IJSObjectReference _MsalModule; 477 | private async Task GetMsalModuleAsync() 478 | { 479 | return _MsalModule ??= await this.JSRuntime.InvokeAsync("import", $"https://alcdn.msftauth.net/browser/{this.Options.MsalVersion}/js/msal-browser.min.js").AsTask(); 480 | } 481 | 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /Blazorade.Msal/Services/BlazoradeRequestFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Blazorade.Msal.Services 10 | { 11 | /// 12 | /// A request factory implementation that can create HTTP requests that are authorized with 13 | /// an access token provided by . 14 | /// 15 | public class BlazoradeRequestFactory 16 | { 17 | /// 18 | /// Creates a new instance of the class. 19 | /// 20 | /// The service dependency to inject. 21 | /// The exception that is thrown if is null. 22 | public BlazoradeRequestFactory(BlazoradeMsalService msalService) 23 | { 24 | this.MsalService = msalService ?? throw new ArgumentNullException(nameof(msalService)); 25 | } 26 | 27 | private readonly BlazoradeMsalService MsalService; 28 | 29 | /// 30 | /// Creates a request with the given options. 31 | /// 32 | /// The options that control how the request will be created and how the access token is acquired. 33 | /// The exception that is thrown if is null. 34 | public async Task CreateRequestAsync(CreateRequestOptions options) 35 | { 36 | if (null == options) throw new ArgumentNullException(nameof(options)); 37 | 38 | var request = new HttpRequestMessage(options.Method, options.RequestUri); 39 | 40 | var authResult = await this.MsalService.AcquireTokenAsync( 41 | loginHint: options.LoginHint, 42 | scopes: options.Scopes, 43 | fallbackToDefaultLoginHint: options.FallbackToDefaultLoginHint, 44 | prompt: options.Prompt 45 | ); 46 | if(authResult?.AccessToken?.Length > 0) 47 | { 48 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken); 49 | } 50 | 51 | return request; 52 | } 53 | 54 | /// 55 | /// Creates a request to the specified with the specified 56 | /// which is authorized with an access token for the given . 57 | /// 58 | /// The URI for the request. 59 | /// The HTTP method for the request. 60 | /// The scopes for the access token that the request will be authorized with. 61 | public async Task CreateRequestAsync(Uri requestUri, HttpMethod method, params string[] scopes) 62 | { 63 | var options = new CreateRequestOptions(requestUri) 64 | { 65 | Method = method, 66 | Scopes = scopes 67 | }; 68 | 69 | return await this.CreateRequestAsync(options); 70 | } 71 | 72 | /// 73 | /// Creates a request to the specified with the specified 74 | /// which is authorized with an access token for the given . 75 | /// 76 | /// The URI for the request. 77 | /// The HTTP method for the request. 78 | /// The scopes for the access token that the request will be authorized with. 79 | public async Task CreateRequestAsync(string requestUri, HttpMethod method, params string[] scopes) 80 | { 81 | return await this.CreateRequestAsync(new Uri(requestUri, UriKind.RelativeOrAbsolute), method, scopes); 82 | } 83 | 84 | /// 85 | /// Creates a DELETE request to which is authorized an access token that 86 | /// defines the scopes specified in . 87 | /// 88 | /// The HTTP method for the request. 89 | /// The scopes for the access token that the request will be authorized with. 90 | public async Task CreateDeleteRequestAsync(Uri requestUri, params string[] scopes) 91 | { 92 | return await this.CreateRequestAsync(requestUri, HttpMethod.Delete, scopes); 93 | } 94 | 95 | /// 96 | /// Creates a DELETE request to which is authorized an access token that 97 | /// defines the scopes specified in . 98 | /// 99 | /// The HTTP method for the request. 100 | /// The scopes for the access token that the request will be authorized with. 101 | public async Task CreateDeleteRequestAsync(string requestUri, params string[] scopes) 102 | { 103 | return await this.CreateRequestAsync(requestUri, HttpMethod.Delete, scopes); 104 | } 105 | 106 | /// 107 | /// Creates a GET request to which is authorized an access token that 108 | /// defines the scopes specified in . 109 | /// 110 | /// The HTTP method for the request. 111 | /// The scopes for the access token that the request will be authorized with. 112 | public async Task CreateGetRequestAsync(Uri requestUri, params string[] scopes) 113 | { 114 | return await this.CreateRequestAsync(requestUri, HttpMethod.Get, scopes); 115 | } 116 | 117 | /// 118 | /// Creates a GET request to which is authorized an access token that 119 | /// defines the scopes specified in . 120 | /// 121 | /// The HTTP method for the request. 122 | /// The scopes for the access token that the request will be authorized with. 123 | public async Task CreateGetRequestAsync(string requestUri, params string[] scopes) 124 | { 125 | return await this.CreateRequestAsync(requestUri, HttpMethod.Get, scopes); 126 | } 127 | 128 | /// 129 | /// Creates a PATCH request to which is authorized an access token that 130 | /// defines the scopes specified in . 131 | /// 132 | /// The HTTP method for the request. 133 | /// The scopes for the access token that the request will be authorized with. 134 | public async Task CreatePatchRequestAsync(Uri requestUri, params string[] scopes) 135 | { 136 | return await this.CreateRequestAsync(requestUri, HttpMethod.Patch, scopes); 137 | } 138 | 139 | /// 140 | /// Creates a PATCH request to which is authorized an access token that 141 | /// defines the scopes specified in . 142 | /// 143 | /// The HTTP method for the request. 144 | /// The scopes for the access token that the request will be authorized with. 145 | public async Task CreatePatchRequestAsync(string requestUri, params string[] scopes) 146 | { 147 | return await this.CreateRequestAsync(requestUri, HttpMethod.Patch, scopes); 148 | } 149 | 150 | /// 151 | /// Creates a POST request to which is authorized an access token that 152 | /// defines the scopes specified in . 153 | /// 154 | /// The HTTP method for the request. 155 | /// The scopes for the access token that the request will be authorized with. 156 | public async Task CreatePostRequestAsync(Uri requestUri, params string[] scopes) 157 | { 158 | return await this.CreateRequestAsync(requestUri, HttpMethod.Post, scopes); 159 | } 160 | 161 | /// 162 | /// Creates a POST request to which is authorized an access token that 163 | /// defines the scopes specified in . 164 | /// 165 | /// The HTTP method for the request. 166 | /// The scopes for the access token that the request will be authorized with. 167 | public async Task CreatePostRequestAsync(string requestUri, params string[] scopes) 168 | { 169 | return await this.CreateRequestAsync(requestUri, HttpMethod.Post, scopes); 170 | } 171 | 172 | /// 173 | /// Creates a PUT request to which is authorized an access token that 174 | /// defines the scopes specified in . 175 | /// 176 | /// The HTTP method for the request. 177 | /// The scopes for the access token that the request will be authorized with. 178 | public async Task CreatePutRequestAsync(Uri requestUri, params string[] scopes) 179 | { 180 | return await this.CreateRequestAsync(requestUri, HttpMethod.Put, scopes); 181 | } 182 | 183 | /// 184 | /// Creates a PUT request to which is authorized an access token that 185 | /// defines the scopes specified in . 186 | /// 187 | /// The HTTP method for the request. 188 | /// The scopes for the access token that the request will be authorized with. 189 | public async Task CreatePutRequestAsync(string requestUri, params string[] scopes) 190 | { 191 | return await this.CreateRequestAsync(requestUri, HttpMethod.Put, scopes); 192 | } 193 | 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /Blazorade.Msal/Services/CreateRequestOptions.cs: -------------------------------------------------------------------------------- 1 | using Blazorade.Msal.Security; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Blazorade.Msal.Services 10 | { 11 | #nullable enable 12 | 13 | public class CreateRequestOptions 14 | { 15 | /// 16 | /// Creates a new class instance and specifies the . 17 | /// 18 | /// The URI for the request. 19 | /// The exception that is thrown if is null. 20 | public CreateRequestOptions(Uri requestUri) 21 | { 22 | this.RequestUri = requestUri ?? throw new ArgumentNullException(nameof(requestUri)); 23 | } 24 | 25 | /// 26 | /// The URI for the request. 27 | /// 28 | /// 29 | /// Can be either absolute or relative, but a relative URI requires that the base URI is set on the HTTP client. 30 | /// 31 | public Uri RequestUri { get; set; } 32 | 33 | /// 34 | /// A collection of scopes required for the request. 35 | /// 36 | public IEnumerable Scopes { get; set; } = Enumerable.Empty(); 37 | 38 | /// 39 | /// The request method. 40 | /// 41 | /// 42 | /// Defaults to . 43 | /// 44 | public HttpMethod Method { get; set; } = HttpMethod.Get; 45 | 46 | /// 47 | /// A login hint to use when acquiring tokens. 48 | /// 49 | public string? LoginHint { get; set; } 50 | 51 | /// 52 | /// Specifies whether to use a previously used login hint if is not specified. 53 | /// 54 | public bool FallbackToDefaultLoginHint { get; set; } = true; 55 | 56 | /// 57 | /// How to prompt the user to log in in case the user must log in to acquire the required token. 58 | /// 59 | public LoginPrompt? Prompt { get; set; } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Blazorade.Msal/Services/TokenAcquisitionRequest.cs: -------------------------------------------------------------------------------- 1 | using Blazorade.Msal.Security; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Blazorade.Msal.Services 9 | { 10 | /// 11 | /// A request object for acquiring tokens. 12 | /// 13 | /// 14 | /// Not all token acquisition methods support all of the properties on this class. Properties that are not supported are ignored. 15 | /// 16 | public class TokenAcquisitionRequest 17 | { 18 | /// 19 | /// Creates a new instance of the request class. 20 | /// 21 | public TokenAcquisitionRequest() 22 | { 23 | } 24 | 25 | 26 | /// 27 | /// Specifies whether to fall back to the default login hint. The default login hint is the login hint that 28 | /// was previously used to acquire a token. 29 | /// 30 | /// 31 | /// This is used in certain cases where the login hint is not known or available. 32 | /// 33 | public bool FallbackToDefaultLoginHint { get; set; } 34 | 35 | /// 36 | /// The number of milliseconds to specify as timeout for token acquisition calls. 37 | /// 38 | /// 39 | /// If the timeout is null, a default value will be determined based on the type of token request. The timeout 40 | /// for interactive token requests is always longer than for silent requests. 41 | /// 42 | public int? Timeout { get; set; } 43 | 44 | /// 45 | /// The username to acquire the token for. If the token cannot be acquired silently, this value will also be 46 | /// passed to the interactive method to minimize the amount of information the user has to enter. 47 | /// 48 | public string LoginHint { get; set; } 49 | 50 | /// 51 | /// The prompt behaviour to use. If not specified, no specific prompt behaviour is used. 52 | /// 53 | public LoginPrompt? Prompt { get; set; } 54 | 55 | /// 56 | /// The scopes to include in the requested access token. If not specified, the default scopes configured on 57 | /// the application is used. 58 | /// 59 | public IEnumerable Scopes { get; set; } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Blazorade.Msal/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | 3 | @using Blazorade.Core 4 | @using Blazorade.Core.Components 5 | @using Blazorade.Core.Components.Builder 6 | 7 | @using Blazorade.Msal 8 | @using Blazorade.Msal.Configuration 9 | @using Blazorade.Msal.Security 10 | @using Blazorade.Msal.Services 11 | -------------------------------------------------------------------------------- /Blazorade.Msal/readme.md: -------------------------------------------------------------------------------- 1 | # Blazorade MSAL 2 | 3 | Provides easy to use authentication and token acquisition for Blazor applications with the help of Microsoft Authentication Library. Supports both Blazor Server and Blazor WebAssembly applications. 4 | 5 | ## Getting Started 6 | 7 | After you have installed the package to your application, refer to the [Getting Started](https://github.com/Blazorade/Blazorade-MSAL/wiki/Getting-Started) section on the package wiki for information on how to easily get started with Blazorade MSAL. 8 | 9 | ## Highlights 10 | 11 | Blazorade MSAL facilitates authentication and authorization for instance with the following services. 12 | 13 | - `BlazoradeMsalService` - A service class that handles all communication with the MSAL JavaScript library for you. You don't have to write a single line of JavaScript code in your application. 14 | - `BlazoradeRequestFactory` - A factory service that creates [`HttpRequestMessage`](https://docs.microsoft.com/dotnet/api/system.net.http.httprequestmessage) instances. These request messages are configured with an access token provided by `BlazoradeMsalService` which enables you to easily call into APIs such as [Microsoft Graph](https://docs.microsoft.com/graph/api/overview). 15 | 16 | These services are registered in your application's service collection with the `AddBlazoradeMsal` method as described in the [Getting Started](https://github.com/Blazorade/Blazorade-MSAL/wiki/Getting-Started#configure-blazorade-msal-for-your-application) section on the Blazoarde MSAL wiki. 17 | 18 | ## Sample Applications 19 | 20 | The Github repository for Blazorade MSAL contains several sample applications that demonstrate how you can leverage Blazorade MSAL in your own application. 21 | 22 | - [**GraphClient**](https://github.com/Blazorade/Blazorade-MSAL/tree/main/GraphClient) - A WebAssembly application that demonstrates how to send HTTP requests to Microsoft Graph with the help of Blazorade MSAL. Can be applied to any other REST API that requires access tokens. 23 | - [**BlazorServerSample**](https://github.com/Blazorade/Blazorade-MSAL/tree/main/BlazorServerSample) - A Blazor Server application that demonstrates how you can make use of the on-demand token acquisition provided by Blazorade MSAL. 24 | - [**BlazorWasmSample**](https://github.com/Blazorade/Blazorade-MSAL/tree/main/BlazorWasmSample) - The same as BlazorServerSample but implemented as a Blazor WebAssembly application. Shares much of the features with the Server sample through the [SharedComponentsSample](https://github.com/Blazorade/Blazorade-MSAL/tree/main/SharedComponentsSample) component library. 25 | 26 | ## Version Highlights 27 | 28 | This section lists the main improvements in each published version. 29 | 30 | ### v2.2.0 31 | 32 | Added support for Azure AD B2C as Identity Provider. 33 | 34 | - [Azure AD B2C Support](https://github.com/Blazorade/Blazorade-MSAL/pull/26) 35 | - [Getting Started](https://github.com/Blazorade/Blazorade-MSAL/wiki/Getting-Started): Added documentation to the Getting Started section on how to configure your application for Azure AD B2C. 36 | 37 | ### v2.1.0 38 | 39 | This version includes the following pull requests. 40 | 41 | - [#24 Changed methods on `BlazoradeMsalService` to virtual](https://github.com/Blazorade/Blazorade-MSAL/pull/24) 42 | - [#25 Added HasTokenAsync method](https://github.com/Blazorade/Blazorade-MSAL/pull/25) 43 | 44 | ## Further Reading 45 | 46 | To learn more, read these [Blazorade MSAL articles](https://mikaberglund.com/tag/blazorade-msal/) on Mika Berglund's blog. -------------------------------------------------------------------------------- /Blazorade.Msal/wwwroot/js/blazoradeMsal.js: -------------------------------------------------------------------------------- 1 |  2 | export function acquireTokenSilent(args) { 3 | console.debug("acquireTokenSilent", args); 4 | 5 | let msalClient = createMsalClient(args); 6 | console.debug("acquireTokenSilent", "msalClient", msalClient); 7 | 8 | let account = msalClient.getAccountByUsername(args.data.loginHint); 9 | 10 | if (!account && args.data.fallbackToDefaultLoginHint) { 11 | console.debug("acquireTokenSilent", "Provided login hint did not find an account. Falling back to previously stored default login hint."); 12 | account = getDefaultAccount(args, msalClient); 13 | } 14 | 15 | if (!account) { 16 | // If we still don't have an account, then we need to log a warning. 17 | console.warn("acquireTokenSilent", "loginHint did not find an account. Tokens can most likely not be acquired without a found account."); 18 | } 19 | 20 | console.debug("acquireTokenSilent", "account", account); 21 | 22 | let request = { 23 | account: account, 24 | scopes: args.data.scopes, 25 | authority: args.data.msalConfig.auth.authority 26 | }; 27 | console.debug("acquireTokenSilent", "request", request); 28 | 29 | try { 30 | msalClient.acquireTokenSilent(request) 31 | .then(result => { 32 | console.debug("acquireTokenSilent", "success", result); 33 | setDefaultLoginHint(args, result); 34 | invokeCallback(args.successCallback, result); 35 | }).catch(err => { 36 | console.warn("acquireTokenSilent", "failure", err); 37 | invokeCallback(args.failureCallback, err); 38 | }); 39 | } 40 | catch (err) { 41 | console.error("acquireTokenSilent", "Failure calling MSAL client", err); 42 | invokeCallback(args.failureCallback, err); 43 | } 44 | } 45 | 46 | export function acquireTokenPopup(args) { 47 | console.debug("acquireTokenPopup", args); 48 | 49 | let msalClient = createMsalClient(args); 50 | console.debug("acquireTokenPopup", "msalClient", msalClient); 51 | 52 | let request = { 53 | scopes: args.data.scopes, 54 | authority: args.data.msalConfig.auth.authority, 55 | loginHint: args.data.loginHint 56 | }; 57 | if (args.data.prompt) request["prompt"] = args.data.prompt; 58 | console.debug("acquireTokenPopup", "request", request); 59 | 60 | try { 61 | msalClient.acquireTokenPopup(request) 62 | .then(result => { 63 | console.debug("acquireTokenPopup", "success", result); 64 | setDefaultLoginHint(args, result); 65 | invokeCallback(args.successCallback, result); 66 | }) 67 | .catch(err => { 68 | console.error("acquireTokenPopup", "failure", err); 69 | invokeCallback(args.failureCallback, err); 70 | }); 71 | } 72 | catch (err) { 73 | console.error("acquireTokenPopup", "Failure calling MSAL client", err); 74 | invokeCallback(args.failureCallback, err); 75 | } 76 | } 77 | 78 | export function acquireTokenRedirect(args) { 79 | console.debug("acquireTokenRedirect", args); 80 | 81 | let msalClient = createMsalClient(args); 82 | console.debug("acquireTokenRedirect", "msalClient", msalClient); 83 | 84 | let request = { 85 | scopes: args.data.scopes, 86 | authority: args.data.msalConfig.auth.authority, 87 | loginHint: args.data.loginHint, 88 | redirectUri: args.data.msalConfig.auth.redirectUri 89 | }; 90 | if (args.data.prompt) request["prompt"] = args.data.prompt; 91 | console.debug("acquireTokenRedirect", "request", request); 92 | 93 | try { 94 | msalClient.acquireTokenRedirect(request); 95 | invokeCallback(args.successCallback, null); 96 | } 97 | catch (err) { 98 | console.error("acquireTokenRedirect", "Failure calling MSAL client", err); 99 | invokeCallback(args.failureCallback, err); 100 | } 101 | } 102 | 103 | export function getDefaultLoginHint(args) { 104 | console.debug("getDefaultLoginHint", args); 105 | 106 | try { 107 | let loginHint = getDefaultLoginHintInternal(args); 108 | console.debug("getDefaultLoginHint", "loginHint", loginHint); 109 | invokeCallback(args.successCallback, loginHint); 110 | } 111 | catch (err) { 112 | invokeCallback(args.failureCallback, err); 113 | } 114 | } 115 | 116 | export function handleRedirectPromise(args) { 117 | console.debug("handleRedirectPromise", args); 118 | 119 | let msalClient = createMsalClient(args); 120 | console.debug("handleRedirectPromise", "msalClient", msalClient); 121 | 122 | try { 123 | msalClient.handleRedirectPromise() 124 | .then(result => { 125 | console.debug("handleRedirectPromise", "success", result); 126 | setDefaultLoginHint(args, result); 127 | invokeCallback(args.successCallback, result); 128 | }) 129 | .catch(err => { 130 | console.error("handleRedirectPromise", "failure", err); 131 | invokeCallback(args.failureCallback, err); 132 | }); 133 | } 134 | catch (err) { 135 | console.error("handleRedirectPromise", "Failure calling MSAL client", err); 136 | invokeCallback(args.failureCallback, err); 137 | } 138 | } 139 | 140 | export function logout(args) { 141 | console.debug("logout", args); 142 | 143 | clearDefaultLoginHint(args); 144 | 145 | let msalClient = createMsalClient(args); 146 | let request = {}; 147 | let logoutUrl = getLogoutUrl(args); 148 | if (logoutUrl) { 149 | request["postLogoutRedirectUri"] = logoutUrl; 150 | } 151 | 152 | console.debug("logout", "request", request); 153 | 154 | try { 155 | msalClient.logout(request); 156 | invokeCallback(args.successCallback); 157 | } 158 | catch (err) { 159 | invokeCallback(args.failureCallback, err); 160 | } 161 | } 162 | 163 | 164 | 165 | function clearDefaultLoginHint(args) { 166 | console.debug("clearDefaultLoginHint", args); 167 | let key = createDefaultLoginHintKey(args); 168 | window.localStorage.removeItem(key); 169 | } 170 | 171 | function createDefaultLoginHintKey(args) { 172 | return `${args.data.msalConfig.auth.clientId}.blazorade-default-loginHint`; 173 | } 174 | 175 | function createMsalClient(args) { 176 | console.debug("createMsalClient", args); 177 | setMsalConfigDefault(args.data.msalConfig); 178 | 179 | let msalClient = new msal.PublicClientApplication(args.data.msalConfig); 180 | console.debug("createMsalClient", "msalClient", msalClient); 181 | 182 | return msalClient; 183 | } 184 | 185 | function getDefaultAccount(args, msalClient) { 186 | console.debug("getDefaultAccount", args, msalClient); 187 | 188 | let account; 189 | let loginHint = getDefaultLoginHintInternal(args); 190 | console.debug("getDefaultAccount", "Default login hint", loginHint); 191 | 192 | if (loginHint) { 193 | account = msalClient.getAccountByUsername(loginHint); 194 | console.debug("getDefaultAccount", "Using default account", account); 195 | } 196 | 197 | return account; 198 | } 199 | 200 | function getDefaultLoginHintInternal(args) { 201 | console.debug("getDefaultLoginHintInternal", args); 202 | 203 | let key = createDefaultLoginHintKey(args); 204 | console.debug("getDefaultLoginHintInternal", "key", key); 205 | 206 | let loginHint = window.localStorage.getItem(key); 207 | console.debug("getDefaultLoginHintInternal", "loginHint", loginHint); 208 | 209 | return loginHint; 210 | } 211 | 212 | function getLogoutUrl(args) { 213 | 214 | if (args && args.msalConfig && args.msalConfig.auth) { 215 | return args.msalConfig.auth["postLogoutRedirectUri"]; 216 | } 217 | return null; 218 | } 219 | 220 | function invokeCallback(callback, ...args) { 221 | console.debug("invokeCallback", callback, args); 222 | if (callback && callback.target && callback.methodName) { 223 | callback.target.invokeMethodAsync(callback.methodName, ...args); 224 | } 225 | else { 226 | console.error("invokeCallbck", "Given method reference cannot be used for invoking a callback.", callback, args); 227 | } 228 | } 229 | 230 | function setDefaultLoginHint(args, authResult) { 231 | console.debug("setDefaultLoginHint", args, authResult); 232 | 233 | if (authResult && authResult.account && authResult.account.username) { 234 | console.debug("setDefaultLoginHint", authResult.account.username); 235 | 236 | let key = createDefaultLoginHintKey(args); 237 | window.localStorage.setItem(key, authResult.account.username); 238 | console.debug("key", key); 239 | } 240 | } 241 | 242 | function setMsalConfigDefault(msalConfig) { 243 | console.debug("setMsalConfigDefault", msalConfig); 244 | 245 | msalConfig.auth = msalConfig.auth ? msalConfig.auth : {}; 246 | msalConfig.auth.redirectUri = msalConfig.auth.redirectUri ? msalConfig.auth.redirectUri : window.location.origin; 247 | } 248 | -------------------------------------------------------------------------------- /GraphClient/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /GraphClient/GraphClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /GraphClient/Pages/Help.razor: -------------------------------------------------------------------------------- 1 | @page "/help" 2 | @inject NavigationManager NavMan; 3 | 4 | @code { 5 | private string GetRedirectUri() 6 | { 7 | var uri = new Uri(this.NavMan.Uri); 8 | return $"{uri.Scheme}://{uri.Host}:{uri.Port}"; 9 | } 10 | } 11 | Configuring the Sample 12 | 13 |

How to Configure This Sample Application

14 |

15 | This page describes what you need to do to get this application running on your local machine. 16 | This information is also described in the readme.md file in the root folder for the project. 17 |

18 | 19 | 20 |

Azure AD Application Registration

21 |

22 | The first thing you need to do, is to create an application registration in Azure AD. 23 |

24 |
    25 |
  1. Log in to the Azure AD Portal and go to the App Registrations blade
  2. 26 |
  3. Create a new application registration by clicking the New registration button.
  4. 27 |
  5. Given the application a name, for instance Microsoft Graph with Blazorade MSAL
  6. 28 |
  7. Under Redirect URI, select Single-page application (SPA), and set the redirect URI to @this.GetRedirectUri()
  8. 29 |
  9. Click Register
  10. 30 |
  11. Go to the Authentication blade for the application you just registered
  12. 31 |
  13. Under Implicit grant and hybrid flows make sure that both Access tokens and ID tokens checkboxes are checked
  14. 32 |
  15. Save the changes to the application
  16. 33 |
  17. Go to the Overview blade and copy the Application (client) ID and Directory (tenant) ID into the configuration file described below.
  18. 34 |
35 | 36 |

Application Settings

37 |

38 | The application settings file is not included in the source code, so you need to create it. The file is located in the wwwroot folder 39 | and must be called appsettings.json. The contents of the file is shown below. 40 |

41 | 42 |
43 | {
44 |     "app": {
45 |         "clientId": "<Your application ID>",
46 |         "tenantId": "<Your tenant ID>"
47 |     }
48 | }
49 | 
50 | 51 |

The clientId and tenantId are copied from the application registration described above.

52 | -------------------------------------------------------------------------------- /GraphClient/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @inject HttpClient HttpClient; 3 | @inject BlazoradeMsalService MsalService; 4 | @inject BlazoradeRequestFactory RequestFactory; 5 | 6 | @code { 7 | private string? meJson; 8 | private string? driveJson; 9 | 10 | private const string GraphBaseUri = "https://graph.microsoft.com/v1.0"; 11 | 12 | private async Task GetUserObjectAsync() 13 | { 14 | var request = await this.RequestFactory.CreateGetRequestAsync($"{GraphBaseUri}/me", "User.Read"); 15 | var response = await this.HttpClient.SendAsync(request); 16 | if(response.IsSuccessStatusCode) 17 | { 18 | this.meJson = await response.Content.ReadAsStringAsync(); 19 | this.StateHasChanged(); 20 | } 21 | } 22 | 23 | private async Task GetOneDriveDriveAsync() 24 | { 25 | var request = await this.RequestFactory.CreateGetRequestAsync($"{GraphBaseUri}/me/drive", "Files.Read"); 26 | var response = await this.HttpClient.SendAsync(request); 27 | if (response.IsSuccessStatusCode) 28 | { 29 | this.driveJson = await response.Content.ReadAsStringAsync(); 30 | this.StateHasChanged(); 31 | } 32 | } 33 | } 34 | 35 | Calling Microsoft Graph 36 | 37 |

Read Graph Data

38 |

39 | Click the button below to read the following data by connecting to Microsoft Graph on behalf of the currently logged in user. 40 |

41 |
    42 |
  • User object
  • 43 |
  • OneDrive drive
  • 44 |
45 | 46 | 47 | 48 | 49 | @if(this.meJson?.Length > 0 || this.driveJson?.Length > 0) 50 | { 51 | 52 | } 53 |
54 | 55 | @if(this.meJson?.Length > 0) 56 | { 57 |

Current User

58 | @DateTime.Now 59 |
@this.meJson
60 | } 61 | 62 | @if(this.driveJson?.Length > 0) 63 | { 64 |

Current User's OneDrive

65 | @DateTime.Now 66 |
@this.driveJson
67 | } 68 | 69 |
70 | -------------------------------------------------------------------------------- /GraphClient/Pages/LoggedOut.razor: -------------------------------------------------------------------------------- 1 | @page "/loggedout" 2 | 3 | Logged Out 4 |

Logged Out

5 | 6 | Click here to get back to the home page. 7 | -------------------------------------------------------------------------------- /GraphClient/Program.cs: -------------------------------------------------------------------------------- 1 | using Blazorade.Msal.Configuration; 2 | using GraphClient; 3 | using Microsoft.AspNetCore.Components.Web; 4 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 5 | 6 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 7 | builder.RootComponents.Add("#app"); 8 | builder.RootComponents.Add("head::after"); 9 | 10 | builder.Services 11 | .AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }) 12 | .AddBlazoradeMsal((sp, options) => 13 | { 14 | var root = sp.GetService() ?? throw new Exception($"Cannot get service instance for {typeof(IConfiguration).FullName}."); 15 | var config = root.GetSection("app"); 16 | config.Bind(options); 17 | 18 | options.InteractiveLoginMode = InteractiveLoginMode.Popup; 19 | options.PostLogoutUrl = "/loggedout"; 20 | options.TokenCacheScope = TokenCacheScope.Persistent; 21 | }) 22 | ; 23 | 24 | await builder.Build().RunAsync(); 25 | -------------------------------------------------------------------------------- /GraphClient/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:34730", 7 | "sslPort": 44351 8 | } 9 | }, 10 | "profiles": { 11 | "GraphClient": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 16 | "applicationUrl": "https://localhost:7156;http://localhost:5156", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /GraphClient/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | 7 | 8 |
9 |
10 | About 11 |
12 | 13 |
14 | @Body 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /GraphClient/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row:not(.auth) { 41 | display: none; 42 | } 43 | 44 | .top-row.auth { 45 | justify-content: space-between; 46 | } 47 | 48 | .top-row ::deep a, .top-row ::deep .btn-link { 49 | margin-left: 0; 50 | } 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .page { 55 | flex-direction: row; 56 | } 57 | 58 | .sidebar { 59 | width: 250px; 60 | height: 100vh; 61 | position: sticky; 62 | top: 0; 63 | } 64 | 65 | .top-row { 66 | position: sticky; 67 | top: 0; 68 | z-index: 1; 69 | } 70 | 71 | .top-row.auth ::deep a:first-child { 72 | flex: 1; 73 | text-align: right; 74 | width: 0; 75 | } 76 | 77 | .top-row, article { 78 | padding-left: 2rem !important; 79 | padding-right: 1.5rem !important; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /GraphClient/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  9 | 10 |
11 | 23 |
24 | 25 | @code { 26 | private bool collapseNavMenu = true; 27 | 28 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 29 | 30 | private void ToggleNavMenu() 31 | { 32 | collapseNavMenu = !collapseNavMenu; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /GraphClient/Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /GraphClient/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using GraphClient; 10 | @using GraphClient.Shared 11 | @using Blazorade.Msal; 12 | @using Blazorade.Msal.Components; 13 | @using Blazorade.Msal.Services; -------------------------------------------------------------------------------- /GraphClient/readme.md: -------------------------------------------------------------------------------- 1 | # Graph Client Sample Application 2 | 3 | This sample application demonstrates how you use Blazorade MSAL to acquire an access token that you can use to access Microsoft Graph on behalf of the logged in user. 4 | 5 | ## Required Configuration 6 | 7 | In order to be able to run this sample application on your local machine, you need to add your configuration information to it. Make sure that you have an application settings file available at `wwwroot/appsettings.json`. This file may contain sensitive information and is excluded from source control. 8 | 9 | The contents of that file are described below. 10 | 11 | ``` JSON 12 | { 13 | "app": { 14 | "clientId": , 15 | "tenantId": 16 | } 17 | } 18 | ``` 19 | 20 | - `clientId`: The application ID (client ID) of the Azure AD registered application. 21 | - `tenantId`: The tenant ID of the application. 22 | 23 | ## Azure AD Application Registration 24 | 25 | Follow the steps below to register an application with Azure AD that you can use for this sample application. 26 | 27 | 1. Log in to the [Azure AD portal](https://aad.portal.azure.com/) and go to [App Registrations](https://aad.portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps). 28 | 2. Create a new application registration by clicking the *New registration* button. 29 | 3. Give the application a name. 30 | 4. Under *Redirect URI*, select Single-page application (SPA), and set the redirect URI to *https://localhost:7156/* (or whatever URI you have configured your sample to run on.) 31 | 5. Click *Register*. 32 | 6. Go to the *Authentication* blade for the application. 33 | 7. Under *Implicit grant and hybrid flows* make sure that both *Access tokens* and *ID tokens* checkboxes are checked. 34 | 8. Save the changes to the application registration. 35 | 9. Go to the *Overview* blade and copy the *Application (client) ID* and *Directory (tenant) ID* and store them in the configuration file described above. 36 | -------------------------------------------------------------------------------- /GraphClient/wwwroot/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | appsettings.json 3 | -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | h1:focus { 8 | outline: none; 9 | } 10 | 11 | a, .btn-link { 12 | color: #0071c1; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .content { 22 | padding-top: 1.1rem; 23 | } 24 | 25 | .valid.modified:not([type=checkbox]) { 26 | outline: 1px solid #26b050; 27 | } 28 | 29 | .invalid { 30 | outline: 1px solid red; 31 | } 32 | 33 | .validation-message { 34 | color: red; 35 | } 36 | 37 | #blazor-error-ui { 38 | background: lightyellow; 39 | bottom: 0; 40 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 41 | display: none; 42 | left: 0; 43 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 44 | position: fixed; 45 | width: 100%; 46 | z-index: 1000; 47 | } 48 | 49 | #blazor-error-ui .dismiss { 50 | cursor: pointer; 51 | position: absolute; 52 | right: 0.75rem; 53 | top: 0.5rem; 54 | } 55 | 56 | .blazor-error-boundary { 57 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 58 | padding: 1rem 1rem 1rem 3.7rem; 59 | color: white; 60 | } 61 | 62 | .blazor-error-boundary::after { 63 | content: "An error has occurred." 64 | } 65 | -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/GraphClient/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/GraphClient/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/GraphClient/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /GraphClient/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/GraphClient/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /GraphClient/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/GraphClient/wwwroot/favicon.ico -------------------------------------------------------------------------------- /GraphClient/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/GraphClient/wwwroot/icon-192.png -------------------------------------------------------------------------------- /GraphClient/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GraphClient 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Loading...
16 | 17 |
18 | An unhandled error has occurred. 19 | Reload 20 | 🗙 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GraphClient/wwwroot/sample-data/weather.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2018-05-06", 4 | "temperatureC": 1, 5 | "summary": "Freezing" 6 | }, 7 | { 8 | "date": "2018-05-07", 9 | "temperatureC": 14, 10 | "summary": "Bracing" 11 | }, 12 | { 13 | "date": "2018-05-08", 14 | "temperatureC": -13, 15 | "summary": "Freezing" 16 | }, 17 | { 18 | "date": "2018-05-09", 19 | "temperatureC": -16, 20 | "summary": "Balmy" 21 | }, 22 | { 23 | "date": "2018-05-10", 24 | "temperatureC": -2, 25 | "summary": "Chilly" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Blazorade 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 | -------------------------------------------------------------------------------- /MsalTestConsole/MsalTestConsole.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /MsalTestConsole/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace MsalTestConsole 6 | { 7 | class Program 8 | { 9 | static async Task Main(string[] args) 10 | { 11 | var clientId = args[0]; 12 | var tenantId = args[1]; 13 | var upn = args[2]; 14 | 15 | var app = GetApp(clientId, tenantId); 16 | var account = await app.GetAccountAsync(upn); 17 | 18 | AuthenticationResult token = null; 19 | if(null != account) 20 | { 21 | 22 | } 23 | else 24 | { 25 | token = await app.AcquireTokenInteractive(new string[] { ".default" }) 26 | .WithLoginHint(upn) 27 | .WithPrompt(Prompt.NoPrompt) 28 | .ExecuteAsync(); 29 | } 30 | 31 | Console.WriteLine("Hello World!"); 32 | } 33 | 34 | 35 | static IPublicClientApplication GetApp(string clientId, string tenantId) 36 | { 37 | var app = PublicClientApplicationBuilder 38 | .Create(clientId) 39 | .WithTenantId(tenantId) 40 | .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") 41 | //.WithDefaultRedirectUri() 42 | .Build(); 43 | 44 | return app; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MsalTestConsole/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MsalTestConsole": { 4 | "commandName": "Project", 5 | "commandLineArgs": "d69feea1-513d-472b-b129-9d027c782fdb mikabdev.onmicrosoft.com mika@mikabdev.onmicrosoft.com" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | The Blazorade MSAL library has been deprecated and this repository has been archived. This is done mainly because of the problems when using Blazorade MSAL in [.NET MAUI Blazor Hybrid applications](https://learn.microsoft.com/aspnet/core/blazor/hybrid/tutorials/maui). 4 | 5 | Another Blazorade library will come out that focuses on authentication. This library will support any kind of application on any platform that Blazor is supported on, including .NET MAUI Blazor Hybrid applications. 6 | 7 | This new Blazorade library is now publicly available at [Github/Blazorade/Blazorade-Id](https://github.com/Blazorade/Blazorade-Id). Note that this library is still in early development at the time of writing this (Feb 2024). 8 | 9 | Until then, this repository will be available as an archived, read-only repository. The [Nuget package for Blazorade MSAL](https://www.nuget.org/packages/Blazorade.Msal) will also be available, but marked as a legacy package. 10 | 11 | # Blazorade MSAL 12 | 13 | A Blazor component library that makes it easy to use authentication in your application through MSAL, both in Blazor Server and Blazor WebAssembly applications. 14 | 15 | ## Documentation 16 | 17 | [See the Wiki](https://github.com/Blazorade/Blazorade-MSAL/wiki) for detailed documentation on how to use Blazorade MSAL. 18 | -------------------------------------------------------------------------------- /SharedComponentsSample/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  2 | Home | Subpage 3 |
-------------------------------------------------------------------------------- /SharedComponentsSample/SharedComponentsSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SharedComponentsSample/TokenView.razor: -------------------------------------------------------------------------------- 1 | @inject BlazoradeMsalService msalService 2 | 3 | @code { 4 | 5 | private string scopeString = null; 6 | private string loginHint = null; 7 | private AuthenticationResult token = null; 8 | private bool isLoading = false; 9 | private List errors = new List(); 10 | 11 | protected async override Task OnAfterRenderAsync(bool firstRender) 12 | { 13 | await base.OnAfterRenderAsync(firstRender); 14 | 15 | if (firstRender) 16 | { 17 | this.BeginLoading(); 18 | try 19 | { 20 | this.token = await this.msalService.AcquireTokenSilentAsync(fallbackToDefaultLoginHint: true); 21 | } 22 | catch { } 23 | finally 24 | { 25 | await this.SetUIFromTokenAsync(); 26 | this.EndLoading(); 27 | } 28 | } 29 | } 30 | 31 | private async Task GetTokenAsync(MouseEventArgs e) 32 | { 33 | await this.GetTokenAsync(); 34 | } 35 | 36 | private async Task GetTokenAsync(LoginPrompt? prompt = null) 37 | { 38 | this.BeginLoading(); 39 | 40 | try 41 | { 42 | var scopes = (this.scopeString ?? "").Split(','); 43 | this.token = await this.msalService.AcquireTokenAsync(loginHint: this.loginHint, scopes: scopes, prompt: prompt); 44 | } 45 | catch (Exception ex) 46 | { 47 | this.errors.Add(ex); 48 | } 49 | 50 | await this.SetUIFromTokenAsync(); 51 | 52 | this.EndLoading(); 53 | } 54 | 55 | private async Task LogoutAsync(MouseEventArgs e) 56 | { 57 | await this.msalService.LogoutAsync(); 58 | } 59 | 60 | private void BeginLoading() 61 | { 62 | this.isLoading = true; 63 | this.StateHasChanged(); 64 | } 65 | 66 | private void EndLoading() 67 | { 68 | this.isLoading = false; 69 | this.StateHasChanged(); 70 | } 71 | 72 | private async Task SetUIFromTokenAsync() 73 | { 74 | try 75 | { 76 | if (null != this.token) 77 | { 78 | this.loginHint = this.token.Account?.UserName; 79 | this.scopeString = string.Join(',', this.token.Scopes); 80 | } 81 | else 82 | { 83 | this.loginHint = await this.msalService.GetDefaultLoginHintAsync(); 84 | } 85 | } 86 | catch (Exception ex) 87 | { 88 | this.errors.Add(ex); 89 | } 90 | } 91 | 92 | } 93 | 94 |
95 | 96 | 97 |
98 | 105 | 107 | 113 | 114 |
115 |
116 | 117 | @if (null != this.token) 118 | { 119 |

Token for: @this.token.Account?.Name

120 |
    121 |
  • Account Name: @this.token.Account?.Name
  • 122 |
  • Account Username: @this.token.Account?.UserName
  • 123 |
  • Authority: @this.token.Authority
  • 124 |
  • Expires On: @this.token.ExpiresOn
  • 125 |
  • Scopes: @string.Join(", ", this.token.Scopes?.ToArray() ?? new string[0])
  • 126 |
  • 127 | Access Token: @this.token.AccessToken?.Length chars 128 | @if(this.token.AccessToken?.Length > 0) 129 | { 130 | [view] 131 | } 132 | 133 |
  • 134 |
  • 135 | Id Token: @this.token.IdToken?.Length chars 136 | @if(this.token.IdToken?.Length > 0) 137 | { 138 | [view] 139 | } 140 |
  • 141 |
142 | } 143 | 144 | @if (this.errors.Count > 0) 145 | { 146 |

Errors

147 | 148 | @foreach (var ex in this.errors) 149 | { 150 |
@ex.ToString()
151 |
152 | } 153 | } 154 | -------------------------------------------------------------------------------- /SharedComponentsSample/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | 3 | @using Blazorade.Core 4 | @using Blazorade.Core.Components 5 | @using Blazorade.Core.Components.Server 6 | @using Blazorade.Core.Interop 7 | @using Blazorade.Msal 8 | @using Blazorade.Msal.Configuration 9 | @using Blazorade.Msal.Security 10 | @using Blazorade.Msal.Services 11 | -------------------------------------------------------------------------------- /SharedComponentsSample/wwwroot/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazorade/Blazorade-MSAL/8450da441ef75de1fd58ac02e9df3667099b697c/SharedComponentsSample/wwwroot/background.png -------------------------------------------------------------------------------- /SharedComponentsSample/wwwroot/exampleJsInterop.js: -------------------------------------------------------------------------------- 1 | // This is a JavaScript module that is loaded on demand. It can export any number of 2 | // functions, and may import other JavaScript modules if required. 3 | 4 | export function showPrompt(message) { 5 | return prompt(message, 'Type anything here'); 6 | } 7 | --------------------------------------------------------------------------------