|
Add this skill
npx mdskills install sickn33/microsoft-azure-webjobs-extensions-authentication-events-dotnetComprehensive Azure Functions extension guide with practical event handlers and examples
1---2name: microsoft-azure-webjobs-extensions-authentication-events-dotnet3description: |4 Microsoft Entra Authentication Events SDK for .NET. Azure Functions triggers for custom authentication extensions. Use for token enrichment, custom claims, attribute collection, and OTP customization in Entra ID. Triggers: "Authentication Events", "WebJobsAuthenticationEventsTrigger", "OnTokenIssuanceStart", "OnAttributeCollectionStart", "custom claims", "token enrichment", "Entra custom extension", "authentication extension".5---67# Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents (.NET)89Azure Functions extension for handling Microsoft Entra ID custom authentication events.1011## Installation1213```bash14dotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents15```1617**Current Version**: v1.1.0 (stable)1819## Supported Events2021| Event | Purpose |22|-------|---------|23| `OnTokenIssuanceStart` | Add custom claims to tokens during issuance |24| `OnAttributeCollectionStart` | Customize attribute collection UI before display |25| `OnAttributeCollectionSubmit` | Validate/modify attributes after user submission |26| `OnOtpSend` | Custom OTP delivery (SMS, email, etc.) |2728## Core Workflows2930### 1. Token Enrichment (Add Custom Claims)3132Add custom claims to access or ID tokens during sign-in.3334```csharp35using Microsoft.Azure.WebJobs;36using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;37using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;38using Microsoft.Extensions.Logging;3940public static class TokenEnrichmentFunction41{42 [FunctionName("OnTokenIssuanceStart")]43 public static WebJobsAuthenticationEventResponse Run(44 [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,45 ILogger log)46 {47 log.LogInformation("Token issuance event for user: {UserId}",48 request.Data?.AuthenticationContext?.User?.Id);4950 // Create response with custom claims51 var response = new WebJobsTokenIssuanceStartResponse();5253 // Add claims to the token54 response.Actions.Add(new WebJobsProvideClaimsForToken55 {56 Claims = new Dictionary<string, string>57 {58 { "customClaim1", "customValue1" },59 { "department", "Engineering" },60 { "costCenter", "CC-12345" },61 { "apiVersion", "v2" }62 }63 });6465 return response;66 }67}68```6970### 2. Token Enrichment with External Data7172Fetch claims from external systems (databases, APIs).7374```csharp75using Microsoft.Azure.WebJobs;76using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;77using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;78using Microsoft.Extensions.Logging;79using System.Net.Http;80using System.Text.Json;8182public static class TokenEnrichmentWithExternalData83{84 private static readonly HttpClient _httpClient = new();8586 [FunctionName("OnTokenIssuanceStartExternal")]87 public static async Task<WebJobsAuthenticationEventResponse> Run(88 [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,89 ILogger log)90 {91 string? userId = request.Data?.AuthenticationContext?.User?.Id;9293 if (string.IsNullOrEmpty(userId))94 {95 log.LogWarning("No user ID in request");96 return new WebJobsTokenIssuanceStartResponse();97 }9899 // Fetch user data from external API100 var userProfile = await GetUserProfileAsync(userId);101102 var response = new WebJobsTokenIssuanceStartResponse();103 response.Actions.Add(new WebJobsProvideClaimsForToken104 {105 Claims = new Dictionary<string, string>106 {107 { "employeeId", userProfile.EmployeeId },108 { "department", userProfile.Department },109 { "roles", string.Join(",", userProfile.Roles) }110 }111 });112113 return response;114 }115116 private static async Task<UserProfile> GetUserProfileAsync(string userId)117 {118 var response = await _httpClient.GetAsync($"https://api.example.com/users/{userId}");119 response.EnsureSuccessStatusCode();120 var json = await response.Content.ReadAsStringAsync();121 return JsonSerializer.Deserialize<UserProfile>(json)!;122 }123}124125public record UserProfile(string EmployeeId, string Department, string[] Roles);126```127128### 3. Attribute Collection - Customize UI (Start Event)129130Customize the attribute collection page before it's displayed.131132```csharp133using Microsoft.Azure.WebJobs;134using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;135using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;136using Microsoft.Extensions.Logging;137138public static class AttributeCollectionStartFunction139{140 [FunctionName("OnAttributeCollectionStart")]141 public static WebJobsAuthenticationEventResponse Run(142 [WebJobsAuthenticationEventsTrigger] WebJobsAttributeCollectionStartRequest request,143 ILogger log)144 {145 log.LogInformation("Attribute collection start for correlation: {CorrelationId}",146 request.Data?.AuthenticationContext?.CorrelationId);147148 var response = new WebJobsAttributeCollectionStartResponse();149150 // Option 1: Continue with default behavior151 response.Actions.Add(new WebJobsContinueWithDefaultBehavior());152153 // Option 2: Prefill attributes154 // response.Actions.Add(new WebJobsSetPrefillValues155 // {156 // Attributes = new Dictionary<string, string>157 // {158 // { "city", "Seattle" },159 // { "country", "USA" }160 // }161 // });162163 // Option 3: Show blocking page (prevent sign-up)164 // response.Actions.Add(new WebJobsShowBlockPage165 // {166 // Message = "Sign-up is currently disabled."167 // });168169 return response;170 }171}172```173174### 4. Attribute Collection - Validate Submission (Submit Event)175176Validate and modify attributes after user submission.177178```csharp179using Microsoft.Azure.WebJobs;180using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;181using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;182using Microsoft.Extensions.Logging;183184public static class AttributeCollectionSubmitFunction185{186 [FunctionName("OnAttributeCollectionSubmit")]187 public static WebJobsAuthenticationEventResponse Run(188 [WebJobsAuthenticationEventsTrigger] WebJobsAttributeCollectionSubmitRequest request,189 ILogger log)190 {191 var response = new WebJobsAttributeCollectionSubmitResponse();192193 // Access submitted attributes194 var attributes = request.Data?.UserSignUpInfo?.Attributes;195196 string? email = attributes?["email"]?.ToString();197 string? displayName = attributes?["displayName"]?.ToString();198199 // Validation example: block certain email domains200 if (email?.EndsWith("@blocked.com") == true)201 {202 response.Actions.Add(new WebJobsShowBlockPage203 {204 Message = "Sign-up from this email domain is not allowed."205 });206 return response;207 }208209 // Validation example: show validation error210 if (string.IsNullOrEmpty(displayName) || displayName.Length < 3)211 {212 response.Actions.Add(new WebJobsShowValidationError213 {214 Message = "Display name must be at least 3 characters.",215 AttributeErrors = new Dictionary<string, string>216 {217 { "displayName", "Name is too short" }218 }219 });220 return response;221 }222223 // Modify attributes before saving224 response.Actions.Add(new WebJobsModifyAttributeValues225 {226 Attributes = new Dictionary<string, string>227 {228 { "displayName", displayName.Trim() },229 { "city", attributes?["city"]?.ToString()?.ToUpperInvariant() ?? "" }230 }231 });232233 return response;234 }235}236```237238### 5. Custom OTP Delivery239240Send one-time passwords via custom channels (SMS, email, push notification).241242```csharp243using Microsoft.Azure.WebJobs;244using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;245using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;246using Microsoft.Extensions.Logging;247248public static class CustomOtpFunction249{250 [FunctionName("OnOtpSend")]251 public static async Task<WebJobsAuthenticationEventResponse> Run(252 [WebJobsAuthenticationEventsTrigger] WebJobsOnOtpSendRequest request,253 ILogger log)254 {255 var response = new WebJobsOnOtpSendResponse();256257 string? phoneNumber = request.Data?.OtpContext?.Identifier;258 string? otp = request.Data?.OtpContext?.OneTimeCode;259260 if (string.IsNullOrEmpty(phoneNumber) || string.IsNullOrEmpty(otp))261 {262 log.LogError("Missing phone number or OTP");263 response.Actions.Add(new WebJobsOnOtpSendFailed264 {265 Error = "Missing required data"266 });267 return response;268 }269270 try271 {272 // Send OTP via your SMS provider273 await SendSmsAsync(phoneNumber, $"Your verification code is: {otp}");274275 response.Actions.Add(new WebJobsOnOtpSendSuccess());276 log.LogInformation("OTP sent successfully to {PhoneNumber}", phoneNumber);277 }278 catch (Exception ex)279 {280 log.LogError(ex, "Failed to send OTP");281 response.Actions.Add(new WebJobsOnOtpSendFailed282 {283 Error = "Failed to send verification code"284 });285 }286287 return response;288 }289290 private static async Task SendSmsAsync(string phoneNumber, string message)291 {292 // Implement your SMS provider integration (Twilio, Azure Communication Services, etc.)293 await Task.CompletedTask;294 }295}296```297298### 6. Function App Configuration299300Configure the Function App for authentication events.301302```csharp303// Program.cs (Isolated worker model)304using Microsoft.Extensions.Hosting;305306var host = new HostBuilder()307 .ConfigureFunctionsWorkerDefaults()308 .Build();309310host.Run();311```312313```json314// host.json315{316 "version": "2.0",317 "logging": {318 "applicationInsights": {319 "samplingSettings": {320 "isEnabled": true321 }322 }323 },324 "extensions": {325 "http": {326 "routePrefix": ""327 }328 }329}330```331332```json333// local.settings.json334{335 "IsEncrypted": false,336 "Values": {337 "AzureWebJobsStorage": "UseDevelopmentStorage=true",338 "FUNCTIONS_WORKER_RUNTIME": "dotnet"339 }340}341```342343## Key Types Reference344345| Type | Purpose |346|------|---------|347| `WebJobsAuthenticationEventsTriggerAttribute` | Function trigger attribute |348| `WebJobsTokenIssuanceStartRequest` | Token issuance event request |349| `WebJobsTokenIssuanceStartResponse` | Token issuance event response |350| `WebJobsProvideClaimsForToken` | Action to add claims |351| `WebJobsAttributeCollectionStartRequest` | Attribute collection start request |352| `WebJobsAttributeCollectionStartResponse` | Attribute collection start response |353| `WebJobsAttributeCollectionSubmitRequest` | Attribute submission request |354| `WebJobsAttributeCollectionSubmitResponse` | Attribute submission response |355| `WebJobsSetPrefillValues` | Prefill form values |356| `WebJobsShowBlockPage` | Block user with message |357| `WebJobsShowValidationError` | Show validation errors |358| `WebJobsModifyAttributeValues` | Modify submitted values |359| `WebJobsOnOtpSendRequest` | OTP send event request |360| `WebJobsOnOtpSendResponse` | OTP send event response |361| `WebJobsOnOtpSendSuccess` | OTP sent successfully |362| `WebJobsOnOtpSendFailed` | OTP send failed |363| `WebJobsContinueWithDefaultBehavior` | Continue with default flow |364365## Entra ID Configuration366367After deploying your Function App, configure the custom extension in Entra ID:3683691. **Register the API** in Entra ID → App registrations3702. **Create Custom Authentication Extension** in Entra ID → External Identities → Custom authentication extensions3713. **Link to User Flow** in Entra ID → External Identities → User flows372373### Required App Registration Settings374375```376Expose an API:377 - Application ID URI: api://<your-function-app-name>.azurewebsites.net378 - Scope: CustomAuthenticationExtension.Receive.Payload379380API Permissions:381 - Microsoft Graph: User.Read (delegated)382```383384## Best Practices3853861. **Validate all inputs** — Never trust request data; validate before processing3872. **Handle errors gracefully** — Return appropriate error responses3883. **Log correlation IDs** — Use `CorrelationId` for troubleshooting3894. **Keep functions fast** — Authentication events have timeout limits3905. **Use managed identity** — Access Azure resources securely3916. **Cache external data** — Avoid slow lookups on every request3927. **Test locally** — Use Azure Functions Core Tools with sample payloads3938. **Monitor with App Insights** — Track function execution and errors394395## Error Handling396397```csharp398[FunctionName("OnTokenIssuanceStart")]399public static WebJobsAuthenticationEventResponse Run(400 [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,401 ILogger log)402{403 try404 {405 // Your logic here406 var response = new WebJobsTokenIssuanceStartResponse();407 response.Actions.Add(new WebJobsProvideClaimsForToken408 {409 Claims = new Dictionary<string, string> { { "claim", "value" } }410 });411 return response;412 }413 catch (Exception ex)414 {415 log.LogError(ex, "Error processing token issuance event");416417 // Return empty response - authentication continues without custom claims418 // Do NOT throw - this would fail the authentication419 return new WebJobsTokenIssuanceStartResponse();420 }421}422```423424## Related SDKs425426| SDK | Purpose | Install |427|-----|---------|---------|428| `Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents` | Auth events (this SDK) | `dotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents` |429| `Microsoft.Identity.Web` | Web app authentication | `dotnet add package Microsoft.Identity.Web` |430| `Azure.Identity` | Azure authentication | `dotnet add package Azure.Identity` |431432## Reference Links433434| Resource | URL |435|----------|-----|436| NuGet Package | https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents |437| Custom Extensions Overview | https://learn.microsoft.com/entra/identity-platform/custom-extension-overview |438| Token Issuance Events | https://learn.microsoft.com/entra/identity-platform/custom-extension-tokenissuancestart-setup |439| Attribute Collection Events | https://learn.microsoft.com/entra/identity-platform/custom-extension-attribute-collection |440| GitHub Source | https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents |441
Full transparency — inspect the skill content before installing.