-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
260 lines (228 loc) Β· 11.2 KB
/
Program.cs
File metadata and controls
260 lines (228 loc) Β· 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
using CrossTenantChat.Components;
using CrossTenantChat.Configuration;
using CrossTenantChat.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
using Azure.Core;
using System.Security.Claims;
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
var builder = WebApplication.CreateBuilder(args);
// Configure static web assets for all environments (when running from source)
// This enables component library and wwwroot assets even when not using a publish output
StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration);
// Add Azure Key Vault configuration
var keyVaultUri = builder.Configuration["Azure:KeyVault:VaultUri"];
var keyVaultEnabled = builder.Configuration.GetValue<bool?>("Azure:KeyVault:Enabled") ?? true;
var preferAzureCli = builder.Configuration.GetValue<bool>("Azure:KeyVault:PreferAzureCliCredential");
var enableInteractiveBrowser = builder.Configuration.GetValue<bool>("Azure:KeyVault:EnableInteractiveBrowserCredential");
if (keyVaultEnabled && !string.IsNullOrEmpty(keyVaultUri))
{
try
{
// Build credential chain honoring configuration
var defaultOptions = new DefaultAzureCredentialOptions { ExcludeAzurePowerShellCredential = true };
var tenantId = builder.Configuration["Azure:AzureAd:TenantId"];
var creds = new List<TokenCredential>();
if (preferAzureCli) creds.Add(new AzureCliCredential());
if (enableInteractiveBrowser)
{
creds.Add(new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions
{
TenantId = string.IsNullOrWhiteSpace(tenantId) ? null : tenantId
}));
}
creds.Add(new DefaultAzureCredential(defaultOptions));
TokenCredential credential = creds.Count == 1 ? creds[0] : new ChainedTokenCredential(creds.ToArray());
builder.Configuration.AddAzureKeyVault(new Uri(keyVaultUri), credential);
Console.WriteLine($"β
Connected to Azure Key Vault: {keyVaultUri}");
}
catch (Exception ex)
{
Console.WriteLine($"β οΈ Could not connect to Azure Key Vault: {keyVaultUri} - {ex.Message}");
}
}
// Configure Azure settings
builder.Services.Configure<AzureConfiguration>(builder.Configuration.GetSection("Azure"));
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// Add MVC controllers for authentication
builder.Services.AddControllers();
// Add memory cache for live services
builder.Services.AddMemoryCache();
// Add custom services - using Live services as default
builder.Services.AddScoped<IEntraIdAuthenticationService, LiveEntraIdAuthenticationService>();
builder.Services.AddSingleton<IAzureCommunicationService, LiveAzureCommunicationService>();
builder.Services.AddSingleton<IAcsOperationTracker, AcsOperationTracker>();
Console.WriteLine("π Live Azure Services registered");
// Add authentication
var azureAdClientId = builder.Configuration["Azure:AzureAd:ClientId"];
var azureAdClientSecret = builder.Configuration["Azure:AzureAd:ClientSecret"];
var azureAdTenantId = builder.Configuration["Azure:AzureAd:TenantId"];
// Only configure real authentication if we have valid Azure AD configuration
if (!string.IsNullOrEmpty(azureAdClientId) &&
!azureAdClientId.Contains("your-client-id") &&
!azureAdClientId.Contains("contoso-client-id") &&
!string.IsNullOrEmpty(azureAdClientSecret) &&
!azureAdClientSecret.Contains("your-client-secret") &&
!string.IsNullOrEmpty(azureAdTenantId) &&
!azureAdTenantId.Contains("your-tenant-id") &&
!azureAdTenantId.Contains("contoso-tenant-id") &&
!azureAdTenantId.Contains("12345678-1234-1234-1234"))
{
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc-contoso";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc-contoso", options =>
{
var contosoAuthority = $"{builder.Configuration["Azure:AzureAd:Instance"]?.TrimEnd('/')}/{builder.Configuration["Azure:AzureAd:ContosoTenantId"]}";
options.Authority = contosoAuthority;
options.ClientId = builder.Configuration["Azure:AzureAd:ContosoApp:ClientId"];
options.ClientSecret = builder.Configuration["Azure:AzureAd:ContosoApp:ClientSecret"];
options.CallbackPath = "/signin-oidc-contoso";
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("https://communication.azure.com/.default");
// Add custom claim to identify tenant
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
if (context.Principal?.Identity is ClaimsIdentity identity)
{
identity.AddClaim(new System.Security.Claims.Claim("tenant", "Contoso"));
identity.AddClaim(new System.Security.Claims.Claim("tenant_id", builder.Configuration["Azure:AzureAd:ContosoTenantId"] ?? ""));
}
return Task.CompletedTask;
}
};
})
.AddOpenIdConnect("oidc-fabrikam", options =>
{
var fabrikamAuthority = $"{builder.Configuration["Azure:AzureAd:Instance"]?.TrimEnd('/')}/{builder.Configuration["Azure:AzureAd:FabrikamTenantId"]}";
options.Authority = fabrikamAuthority;
options.ClientId = builder.Configuration["Azure:AzureAd:FabrikamApp:ClientId"];
options.ClientSecret = builder.Configuration["Azure:AzureAd:FabrikamApp:ClientSecret"];
options.CallbackPath = "/signin-oidc-fabrikam";
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("https://communication.azure.com/.default");
// Add custom claim to identify tenant
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
if (context.Principal?.Identity is ClaimsIdentity identity)
{
identity.AddClaim(new System.Security.Claims.Claim("tenant", "Fabrikam"));
identity.AddClaim(new System.Security.Claims.Claim("tenant_id", builder.Configuration["Azure:AzureAd:FabrikamTenantId"] ?? ""));
}
return Task.CompletedTask;
}
};
})
.AddJwtBearer(options =>
{
var contosoAuthority = $"{builder.Configuration["Azure:AzureAd:Instance"]?.TrimEnd('/')}/{builder.Configuration["Azure:AzureAd:ContosoTenantId"]}";
options.Authority = contosoAuthority;
options.Audience = builder.Configuration["Azure:AzureAd:ContosoApp:ClientId"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
Console.WriteLine("β
Azure AD Authentication configured");
}
else
{
// Demo mode - no real authentication
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
})
.AddCookie("Cookies");
Console.WriteLine("π§ͺ Demo mode - Authentication disabled");
}
builder.Services.AddAuthorization();
// Enhanced logging
builder.Services.AddLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
// Use appropriate logging level
logging.SetMinimumLevel(LogLevel.Debug);
logging.AddFilter("CrossTenantChat", LogLevel.Information);
logging.AddFilter("Microsoft.AspNetCore.Authentication", LogLevel.Information);
logging.AddFilter("Azure", LogLevel.Information);
});
var app = builder.Build();
// Log startup information
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("π― Cross-Tenant Chat Application Starting");
logger.LogInformation("Configuration Sources: {ConfigSources}",
string.Join(", ", builder.Configuration.Sources.Select(s => s.GetType().Name)));
logger.LogInformation("π Live Azure Integration Enabled");
logger.LogInformation("β
Real Entra ID Authentication");
logger.LogInformation("β
Live Azure Communication Services");
// Log configuration validation
var acsConnectionString = builder.Configuration["Azure:AzureCommunicationServices:ConnectionString"];
var contosoTenantId = builder.Configuration["Azure:AzureAd:ContosoTenantId"];
var fabrikamTenantId = builder.Configuration["Azure:AzureAd:FabrikamTenantId"];
logger.LogInformation("ACS Connection: {HasAcsConnection}", !string.IsNullOrEmpty(acsConnectionString) ? "β
Configured" : "β Missing");
logger.LogInformation("Contoso Tenant: {ContosoTenant}", !string.IsNullOrEmpty(contosoTenantId) ? "β
Configured" : "β Missing");
logger.LogInformation("Fabrikam Tenant: {FabrikamTenant}", !string.IsNullOrEmpty(fabrikamTenantId) ? "β
Configured" : "β Missing");
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
// Only enable HTTPS redirection when an HTTPS endpoint is configured
var urls = app.Configuration["ASPNETCORE_URLS"];
var httpsConfigured = (!string.IsNullOrEmpty(urls) && urls.Contains("https", StringComparison.OrdinalIgnoreCase))
|| !string.IsNullOrEmpty(app.Configuration["ASPNETCORE_HTTPS_PORT"])
|| !string.IsNullOrEmpty(app.Configuration["Kestrel:Endpoints:Https:Url"])
|| app.Configuration.GetSection("Kestrel:Endpoints:Https").Exists();
if (httpsConfigured)
{
app.UseHttpsRedirection();
}
else
{
logger.LogWarning("HTTPS redirection disabled: no HTTPS endpoint configured. Set 'ASPNETCORE_URLS' to include https or configure Kestrel endpoints to enable.");
}
}
// Enable static files serving
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapControllers();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
logger.LogInformation("π Cross-Tenant Chat Application Started Successfully");
logger.LogInformation("π Ready for live cross-tenant authentication: Fabrikam β Contoso");
app.Run();