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 | Log in to the Azure AD Portal and go to the App Registrations blade
26 | Create a new application registration by clicking the New registration button.
27 | Given the application a name, for instance Microsoft Graph with Blazorade MSAL
28 | Under Redirect URI , select Single-page application (SPA) , and set the redirect URI to @this.GetRedirectUri()
29 | Click Register
30 | Go to the Authentication blade for the application you just registered
31 | Under Implicit grant and hybrid flows make sure that both Access tokens and ID tokens checkboxes are checked
32 | Save the changes to the application
33 | Go to the Overview blade and copy the Application (client) ID and Directory (tenant) ID into the configuration file described below.
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 | await this.GetUserObjectAsync()">Get User Object
47 | await this.GetOneDriveDriveAsync()">Get OneDrive Drive
48 |
49 | @if(this.meJson?.Length > 0 || this.driveJson?.Length > 0)
50 | {
51 | await this.MsalService.LogoutAsync()">Log Out
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 |
71 | If you are running this sample for the first time, please make sure that you check out the help section
72 | for detailed information on how to configure this sample application to run on your local machine.
73 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------