I have been unable to correctly implement the Microsoft Graph API in my web app. The main usage for now is to enable users to create, update, and delete calendar events via certain in-app actions. I.e. creating an appointment.
However, I have added a simple method to my HomeController
to test the config until it is correct. I have a registered an AAD application with the following delegated permissions for Microsoft Graph, all with admin consent:
Microsoft Graph
Additionally, I have the "groups" claim present.
Here's the code and config I've tried to use:
appsettings.json
:
{
"AppConfig": {
"Endpoint": "https://ipsumlorumconfig.azconfig.io"
},
"DownstreamApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read calendars.readwrite"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
HomeController/TestGraph
method:
// Test action method
[HttpGet]
public async Task<IActionResult> TestGraph()
{
try
{
// Fetch the default calendar
var calendar = await _graphServiceClient.Me.Calendar
.GetAsync();
var resultString = $"Calendar Name: {calendar!.Name}\n";
return Content(resultString, "text/plain");
}
catch (ServiceException ex)
{
// Log and handle Graph API errors
_logger.LogError(ex, "Graph API call failed.");
return StatusCode(500, "Graph API call failed.");
}
}
Program.cs
:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using CRMApp.WebApp.Services;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using CRMApp.WebApp.Middleware;
using Microsoft.IdentityModel.Tokens;
using CRMApp.WebApp.Constants;
using CRMApp.Shared.Data;
using CRMApp.Shared;
var builder = WebApplication.CreateBuilder(args);
string[]? initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
// Load configuration files
builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
// Configure Logging
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
// Add services
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
policyBuilder =>
{
policyBuilder.WithOrigins(
"https://crmapp.azurewebsites.net",
"https://crmappcompany.co.za",
"https://crmapp-development.azurewebsites.net",
"https://crmapp-uat.azurewebsites.net",
"https://crmapp-staging.azurewebsites.net"
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// Register IHttpContextAccessor
builder.Services.AddHttpContextAccessor();
// Register ICompositeViewEngine and ITempDataProvider for CustomExceptionMiddleware
builder.Services.AddSingleton<ICompositeViewEngine, CompositeViewEngine>();
builder.Services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
// Configure Azure AD Authentication (all environments use Azure AD)
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ResponseType = "id_token";
options.Scope.Add("profile");
options.Scope.Add("openid");
options.Scope.Add("email");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "preferred_username",
RoleClaimType = "groups"
};
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
var emailClaim = context.Principal?.FindFirst("preferred_username")?.Value;
if (string.IsNullOrEmpty(emailClaim))
{
logger.LogWarning("Email claim is missing in the token.");
context.Fail("Email claim is missing.");
return Task.CompletedTask;
}
logger.LogInformation("Token validated for {Email}", emailClaim);
if (context.Principal == null)
{
logger.LogWarning("Token validated, but Principal is null.");
context.Fail("Principal is null.");
return Task.CompletedTask;
}
foreach (var claim in context.Principal.Claims)
{
logger.LogDebug("Claim Type: {Type}, Value: {Value}", claim.Type, claim.Value);
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError("Authentication failed: {Error}", context.Exception.Message);
return Task.CompletedTask;
},
OnRedirectToIdentityProvider = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Redirecting to Identity Provider for {User}",
context.HttpContext.User.Identity?.Name ?? "Anonymous");
return Task.CompletedTask;
}
};
});
// Define Authorization Policies
builder.Services.AddAuthorizationBuilder()
.AddPolicy("CRMAccessGroupPolicy", policy =>
policy.RequireClaim("groups", GroupConstants.CRMAccessGroup))
.AddPolicy("CRMApprovalAccessGroupPolicy", policy =>
policy.RequireClaim("groups", GroupConstants.CRMApprovalAccessGroup));
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AuthorizeFilter("CRMAccessGroupPolicy"));
});
builder.Services.AddRazorPages();
// Register Application Services
builder.Services.AddScoped<IcsFileGeneratorService>();
builder.Services.AddScoped<IdGenerator>();
builder.Services.AddScoped<ILocationService, LocationService>();
// Configure DbContext with Retry Logic
builder.Services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null
);
});
});
// Register the AppointmentCleanupService
builder.Services.AddScoped<AppointmentCleanupService>();
var app = builder.Build();
// Register CustomExceptionMiddleware as the first middleware
app.UseMiddleware<CustomExceptionMiddleware>();
if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName == "Local")
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("AllowSpecificOrigin");
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
Edit 1: The one error I receive is:
An unhandled exception occurred while processing the request.
MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable<string> scopes, string authenticationScheme, string tenantId, string userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
Edit 2: New code that produces different error is below:
Program.cs
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using EnvisionCRM.WebApp.Services;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using EnvisionCRM.WebApp.Middleware;
using Microsoft.IdentityModel.Tokens;
using EnvisionCRM.WebApp.Constants;
using EnvisionCRM.Shared.Data;
using EnvisionCRM.Shared;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
var builder = WebApplication.CreateBuilder(args);
string[]? initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
// Load configuration files
builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
// Configure Logging
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
// Add services
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
policyBuilder =>
{
policyBuilder.WithOrigins( // Values were removed for StackOverflow.
"https://.azurewebsites.net",
"https://*******",
"https://-development.azurewebsites.net",
"https://-uat.azurewebsites.net",
"https://-staging.azurewebsites.net"
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// Register IHttpContextAccessor
builder.Services.AddHttpContextAccessor();
// Register ICompositeViewEngine and ITempDataProvider for CustomExceptionMiddleware
builder.Services.AddSingleton<ICompositeViewEngine, CompositeViewEngine>();
builder.Services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
// Configure Azure AD Authentication (all environments use Azure AD)
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ResponseType = "code";
options.Scope.Add("profile");
options.Scope.Add("openid");
options.Scope.Add("email");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "preferred_username",
RoleClaimType = "groups"
};
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
var emailClaim = context.Principal?.FindFirst("preferred_username")?.Value;
if (string.IsNullOrEmpty(emailClaim))
{
logger.LogWarning("Email claim is missing in the token.");
context.Fail("Email claim is missing.");
return Task.CompletedTask;
}
logger.LogInformation("Token validated for {Email}", emailClaim);
if (context.Principal == null)
{
logger.LogWarning("Token validated, but Principal is null.");
context.Fail("Principal is null.");
return Task.CompletedTask;
}
foreach (var claim in context.Principal.Claims)
{
logger.LogDebug("Claim Type: {Type}, Value: {Value}", claim.Type, claim.Value);
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError("Authentication failed: {Error}", context.Exception.Message);
return Task.CompletedTask;
},
OnRedirectToIdentityProvider = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Redirecting to Identity Provider for {User}",
context.HttpContext.User.Identity?.Name ?? "Anonymous");
return Task.CompletedTask;
}
};
});
// Define Authorization Policies
builder.Services.AddAuthorizationBuilder()
.AddPolicy("CRMAccessGroupPolicy", policy =>
policy.RequireClaim("groups", GroupConstants.CRMAccessGroup))
.AddPolicy("CRMApprovalAccessGroupPolicy", policy =>
policy.RequireClaim("groups", GroupConstants.CRMApprovalAccessGroup));
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AuthorizeFilter("CRMAccessGroupPolicy"));
});
builder.Services.AddRazorPages();
// Register Application Services
builder.Services.AddScoped<IcsFileGeneratorService>();
builder.Services.AddScoped<IdGenerator>();
builder.Services.AddScoped<ILocationService, LocationService>();
// Configure DbContext with Retry Logic
builder.Services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null
);
});
});
// Register the AppointmentCleanupService
builder.Services.AddScoped<AppointmentCleanupService>();
var app = builder.Build();
// Register CustomExceptionMiddleware as the first middleware
app.UseMiddleware<CustomExceptionMiddleware>();
if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName == "Local")
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("AllowSpecificOrigin");
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
The error is:
warn: Microsoft.Identity.Web.TokenAcquisition[0]
False MSAL 4.67.2.0 MSAL.NetCore .NET 9.0.1 Microsoft Windows 10.0.26100 [2025-01-24 04:19:17Z] Only in-memory caching is used. The cache is not persisted and will be lost if the machine is restarted. It also does not scale for a web app or web API, where the number of users can grow large. In production, web apps and web APIs should use distributed caching like Redis. See https://aka.ms/msal-net-cca-token-cache-serialization
fail: Microsoft.Identity.Web.TokenAcquisition[0]
False MSAL 4.67.2.0 MSAL.NetCore .NET 9.0.1 Microsoft Windows 10.0.26100 [2025-01-24 04:19:17Z] Exception type: Microsoft.Identity.Client.MsalUiRequiredException
, ErrorCode: user_null
HTTP StatusCode 0
CorrelationId 00000000-0000-0000-0000-000000000000
To see full exception details, enable PII Logging. See https://aka.ms/msal-net-logging
at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
at Microsoft.Identity.Client.Internal.Requests.RequestBase.<>c__DisplayClass11_1.<<RunAsync>b__1>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.Identity.Client.Utils.StopwatchService.MeasureCodeBlockAsync(Func`1 codeBlock)
at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
Edit 3: Erro received in private browser
An unhandled exception occurred while processing the request.
MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable<string> scopes, string authenticationScheme, string tenantId, string userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
Edit 4: Program.cs
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.IdentityModel.Tokens;
using EnvisionCRM.WebApp.Constants;
using EnvisionCRM.Shared.Data;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.Extensions.Logging;
var builder = WebApplication.CreateBuilder(args);
// Load configuration files
builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
// Configure Logging
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Information); // Reduced verbosity
// Add services
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll",
policyBuilder =>
{
policyBuilder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// Register IHttpContextAccessor
builder.Services.AddHttpContextAccessor();
// Register ICompositeViewEngine and ITempDataProvider if necessary
builder.Services.AddSingleton<ICompositeViewEngine, CompositeViewEngine>();
builder.Services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
// Configure Azure AD Authentication (retain for Microsoft Graph)
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
// Configure OpenIdConnect options
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ResponseType = OpenIdConnectResponseType.Code;
options.Scope.Add("profile");
options.Scope.Add("openid");
options.Scope.Add("email");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "preferred_username",
RoleClaimType = "groups"
};
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
var emailClaim = context.Principal?.FindFirst("preferred_username")?.Value;
if (string.IsNullOrEmpty(emailClaim))
{
logger.LogWarning("Email claim is missing in the token.");
context.Fail("Email claim is missing.");
return Task.CompletedTask;
}
logger.LogInformation("Token validated for {Email}", emailClaim);
if (context.Principal == null)
{
logger.LogWarning("Token validated, but Principal is null.");
context.Fail("Principal is null.");
return Task.CompletedTask;
}
foreach (var claim in context.Principal.Claims)
{
logger.LogDebug("Claim Type: {Type}, Value: {Value}", claim.Type, claim.Value);
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError("Authentication failed: {Error}", context.Exception.Message);
return Task.CompletedTask;
},
OnRedirectToIdentityProvider = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Redirecting to Identity Provider for {User}",
context.HttpContext.User.Identity?.Name ?? "Anonymous");
return Task.CompletedTask;
}
};
});
// Define Authorization Policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CRMAccessGroupPolicy", policy =>
policy.RequireClaim("groups", GroupConstants.CRMAccessGroup));
options.AddPolicy("CRMApprovalAccessGroupPolicy", policy =>
policy.RequireClaim("groups", GroupConstants.CRMApprovalAccessGroup));
});
// Configure MVC with Authorization
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AuthorizeFilter("CRMAccessGroupPolicy"));
});
builder.Services.AddRazorPages();
// Configure DbContext with Retry Logic
builder.Services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null
);
});
});
var app = builder.Build();
// Removed CustomExceptionMiddleware
if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName == "Local")
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
The error message told us that you hasn't authenticated _graphServiceClient
, because Graph Service Client requires an access token, but you hasn't signed in so that the AcquireTokenSilent call
failed. I think this shall be the expected behavior, as .AddMicrosoftIdentityWebApp(builder.Configuration)
is going to read Azure AD configurations from appsetting.json, but the content you shared doesn't have it.
Your requirment is
enable users to create, update, and delete calendar events via certain in-app actions
and we have 2 options for you. One option is that, here the user might be in admin role so that the user has the ability to manage calendar events for all users in their team. This requires to grant Application permissions instead of delegated permissions you've granted. And you can use codes like
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenantId";
var clientId = "clientId";
var clientSecret = "clientSecret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var requestBody = new Event
{
Subject = "Let's go for lunch",
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = "Does next month work for you?",
},
...
...
};
var res = await graphClient.Users["user_id"].Calendars["{calendar-id}"].Events.PostAsync(requestBody);
Another option is that each user has the ability to manage their own calendar events, so that you continue to use your delegated permission. But this requires a sign-in module which will redirect your website to Microsoft Identity platform so that the user could get authenticated. It will be easy if you create a new MVC app using VS template and choose Microsoft Identity Platform, then you will see a _LoginPartial.cshtml
view in Shared folder.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
builder.Services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI();
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "client_id",
"ClientSecret": "client_secret",
"Domain": "tenantid",
"TenantId": "tenantid",
"CallbackPath": "/signin-oidc"
},
"Graph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read"
},
[Authorize]
public class HomeController : Controller
{
private GraphServiceClient _graphServiceClient;
public HomeController(GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
public async Task<IActionResult> IndexAsync()
{
var res = await _graphServiceClient.Me.GetAsync();
return View();
}
}
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.0" NoWarn="NU1605" />
<PackageReference Include="Microsoft.Identity.Web" Version="2.15.2" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="2.15.2" />
<PackageReference Include="Microsoft.Identity.Web.DownstreamApi" Version="2.15.2" />
<PackageReference Include="Microsoft.Identity.Web.GraphServiceClient" Version="2.15.2" />
Here's the Login view, you should put it in layout like <partial name="_LoginPartial" />
or somewhere you want.
@using System.Security.Principal
<ul class="navbar-nav">
@if (User.Identity?.IsAuthenticated == true)
{
<span class="navbar-text text-dark">Hello @User.Identity?.Name!</span>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a>
</li>
}
</ul>