[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7141EHZTYJ6E4RADE8CRGQ1":3,"skill-same:01M7141EHZTYJ6E4RADE8CRGQ1":62},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":36,"linkOnly":37,"markdownContent":38,"markdownBody":39,"frontMatter":40,"refName":42,"commitSha":43,"skillDirectory":44,"forksCount":14,"repositoryHtmlUrl":45,"repositoryHomepageUrl":6,"ownerHtmlUrl":46,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":47,"install":48,"resources":61},"01M7141EHZTYJ6E4RADE8CRGQ1","microsoft-azure-webjobs-extensions-authentication-events-dotnet",null,"Microsoft Entra Authentication Events SDK for .NET. Azure Functions triggers for custom authentication extensions.","sapsapshen\u002Fai-memory-router","sapsapshen","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F20347959?v=4","01M6XDQEF1DP50DKF37H81J3J5","shenmian","01M6XDQEJPE41Y3DT2763X8ZJN",0,"MIT","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcopilot\u002Fmicrosoft-azure-webjobs-extensions-authentication-events-dotnet\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],"microsoft","dotnet","azure","microsoft azure webjobs extensions authentication events dotnet","authentication","events","microsoft azure webjobs extensions authentication","extensions","2026","sdk","custom","unknown","for","triggers","community","functions","azure functions","2026-11-10 10:12:30",false,"---\nname: microsoft-azure-webjobs-extensions-authentication-events-dotnet\ndescription: Microsoft Entra Authentication Events SDK for .NET. Azure Functions triggers for custom authentication extensions.\nrisk: unknown\nsource: community\ndate_added: '2026-02-27'\n---\n\n# Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents (.NET)\n\nAzure Functions extension for handling Microsoft Entra ID custom authentication events.\n\n## Installation\n\n```bash\ndotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents\n```\n\n**Current Version**: v1.1.0 (stable)\n\n## Supported Events\n\n| Event | Purpose |\n|-------|---------|\n| `OnTokenIssuanceStart` | Add custom claims to tokens during issuance |\n| `OnAttributeCollectionStart` | Customize attribute collection UI before display |\n| `OnAttributeCollectionSubmit` | Validate\u002Fmodify attributes after user submission |\n| `OnOtpSend` | Custom OTP delivery (SMS, email, etc.) |\n\n## Core Workflows\n\n### 1. Token Enrichment (Add Custom Claims)\n\nAdd custom claims to access or ID tokens during sign-in.\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;\nusing Microsoft.Extensions.Logging;\n\npublic static class TokenEnrichmentFunction\n{\n    [FunctionName(\"OnTokenIssuanceStart\")]\n    public static WebJobsAuthenticationEventResponse Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,\n        ILogger log)\n    {\n        log.LogInformation(\"Token issuance event for user: {UserId}\", \n            request.Data?.AuthenticationContext?.User?.Id);\n\n        \u002F\u002F Create response with custom claims\n        var response = new WebJobsTokenIssuanceStartResponse();\n        \n        \u002F\u002F Add claims to the token\n        response.Actions.Add(new WebJobsProvideClaimsForToken\n        {\n            Claims = new Dictionary\u003Cstring, string>\n            {\n                { \"customClaim1\", \"customValue1\" },\n                { \"department\", \"Engineering\" },\n                { \"costCenter\", \"CC-12345\" },\n                { \"apiVersion\", \"v2\" }\n            }\n        });\n\n        return response;\n    }\n}\n```\n\n### 2. Token Enrichment with External Data\n\nFetch claims from external systems (databases, APIs).\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;\nusing Microsoft.Extensions.Logging;\nusing System.Net.Http;\nusing System.Text.Json;\n\npublic static class TokenEnrichmentWithExternalData\n{\n    private static readonly HttpClient _httpClient = new();\n\n    [FunctionName(\"OnTokenIssuanceStartExternal\")]\n    public static async Task\u003CWebJobsAuthenticationEventResponse> Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,\n        ILogger log)\n    {\n        string? userId = request.Data?.AuthenticationContext?.User?.Id;\n        \n        if (string.IsNullOrEmpty(userId))\n        {\n            log.LogWarning(\"No user ID in request\");\n            return new WebJobsTokenIssuanceStartResponse();\n        }\n\n        \u002F\u002F Fetch user data from external API\n        var userProfile = await GetUserProfileAsync(userId);\n        \n        var response = new WebJobsTokenIssuanceStartResponse();\n        response.Actions.Add(new WebJobsProvideClaimsForToken\n        {\n            Claims = new Dictionary\u003Cstring, string>\n            {\n                { \"employeeId\", userProfile.EmployeeId },\n                { \"department\", userProfile.Department },\n                { \"roles\", string.Join(\",\", userProfile.Roles) }\n            }\n        });\n\n        return response;\n    }\n\n    private static async Task\u003CUserProfile> GetUserProfileAsync(string userId)\n    {\n        var response = await _httpClient.GetAsync($\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F{userId}\");\n        response.EnsureSuccessStatusCode();\n        var json = await response.Content.ReadAsStringAsync();\n        return JsonSerializer.Deserialize\u003CUserProfile>(json)!;\n    }\n}\n\npublic record UserProfile(string EmployeeId, string Department, string[] Roles);\n```\n\n### 3. Attribute Collection - Customize UI (Start Event)\n\nCustomize the attribute collection page before it's displayed.\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;\nusing Microsoft.Extensions.Logging;\n\npublic static class AttributeCollectionStartFunction\n{\n    [FunctionName(\"OnAttributeCollectionStart\")]\n    public static WebJobsAuthenticationEventResponse Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsAttributeCollectionStartRequest request,\n        ILogger log)\n    {\n        log.LogInformation(\"Attribute collection start for correlation: {CorrelationId}\",\n            request.Data?.AuthenticationContext?.CorrelationId);\n\n        var response = new WebJobsAttributeCollectionStartResponse();\n\n        \u002F\u002F Option 1: Continue with default behavior\n        response.Actions.Add(new WebJobsContinueWithDefaultBehavior());\n\n        \u002F\u002F Option 2: Prefill attributes\n        \u002F\u002F response.Actions.Add(new WebJobsSetPrefillValues\n        \u002F\u002F {\n        \u002F\u002F     Attributes = new Dictionary\u003Cstring, string>\n        \u002F\u002F     {\n        \u002F\u002F         { \"city\", \"Seattle\" },\n        \u002F\u002F         { \"country\", \"USA\" }\n        \u002F\u002F     }\n        \u002F\u002F });\n\n        \u002F\u002F Option 3: Show blocking page (prevent sign-up)\n        \u002F\u002F response.Actions.Add(new WebJobsShowBlockPage\n        \u002F\u002F {\n        \u002F\u002F     Message = \"Sign-up is currently disabled.\"\n        \u002F\u002F });\n\n        return response;\n    }\n}\n```\n\n### 4. Attribute Collection - Validate Submission (Submit Event)\n\nValidate and modify attributes after user submission.\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;\nusing Microsoft.Extensions.Logging;\n\npublic static class AttributeCollectionSubmitFunction\n{\n    [FunctionName(\"OnAttributeCollectionSubmit\")]\n    public static WebJobsAuthenticationEventResponse Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsAttributeCollectionSubmitRequest request,\n        ILogger log)\n    {\n        var response = new WebJobsAttributeCollectionSubmitResponse();\n\n        \u002F\u002F Access submitted attributes\n        var attributes = request.Data?.UserSignUpInfo?.Attributes;\n        \n        string? email = attributes?[\"email\"]?.ToString();\n        string? displayName = attributes?[\"displayName\"]?.ToString();\n\n        \u002F\u002F Validation example: block certain email domains\n        if (email?.EndsWith(\"@blocked.com\") == true)\n        {\n            response.Actions.Add(new WebJobsShowBlockPage\n            {\n                Message = \"Sign-up from this email domain is not allowed.\"\n            });\n            return response;\n        }\n\n        \u002F\u002F Validation example: show validation error\n        if (string.IsNullOrEmpty(displayName) || displayName.Length \u003C 3)\n        {\n            response.Actions.Add(new WebJobsShowValidationError\n            {\n                Message = \"Display name must be at least 3 characters.\",\n                AttributeErrors = new Dictionary\u003Cstring, string>\n                {\n                    { \"displayName\", \"Name is too short\" }\n                }\n            });\n            return response;\n        }\n\n        \u002F\u002F Modify attributes before saving\n        response.Actions.Add(new WebJobsModifyAttributeValues\n        {\n            Attributes = new Dictionary\u003Cstring, string>\n            {\n                { \"displayName\", displayName.Trim() },\n                { \"city\", attributes?[\"city\"]?.ToString()?.ToUpperInvariant() ?? \"\" }\n            }\n        });\n\n        return response;\n    }\n}\n```\n\n### 5. Custom OTP Delivery\n\nSend one-time passwords via custom channels (SMS, email, push notification).\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;\nusing Microsoft.Extensions.Logging;\n\npublic static class CustomOtpFunction\n{\n    [FunctionName(\"OnOtpSend\")]\n    public static async Task\u003CWebJobsAuthenticationEventResponse> Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsOnOtpSendRequest request,\n        ILogger log)\n    {\n        var response = new WebJobsOnOtpSendResponse();\n\n        string? phoneNumber = request.Data?.OtpContext?.Identifier;\n        string? otp = request.Data?.OtpContext?.OneTimeCode;\n\n        if (string.IsNullOrEmpty(phoneNumber) || string.IsNullOrEmpty(otp))\n        {\n            log.LogError(\"Missing phone number or OTP\");\n            response.Actions.Add(new WebJobsOnOtpSendFailed\n            {\n                Error = \"Missing required data\"\n            });\n            return response;\n        }\n\n        try\n        {\n            \u002F\u002F Send OTP via your SMS provider\n            await SendSmsAsync(phoneNumber, $\"Your verification code is: {otp}\");\n            \n            response.Actions.Add(new WebJobsOnOtpSendSuccess());\n            log.LogInformation(\"OTP sent successfully to {PhoneNumber}\", phoneNumber);\n        }\n        catch (Exception ex)\n        {\n            log.LogError(ex, \"Failed to send OTP\");\n            response.Actions.Add(new WebJobsOnOtpSendFailed\n            {\n                Error = \"Failed to send verification code\"\n            });\n        }\n\n        return response;\n    }\n\n    private static async Task SendSmsAsync(string phoneNumber, string message)\n    {\n        \u002F\u002F Implement your SMS provider integration (Twilio, Azure Communication Services, etc.)\n        await Task.CompletedTask;\n    }\n}\n```\n\n### 6. Function App Configuration\n\nConfigure the Function App for authentication events.\n\n```csharp\n\u002F\u002F Program.cs (Isolated worker model)\nusing Microsoft.Extensions.Hosting;\n\nvar host = new HostBuilder()\n    .ConfigureFunctionsWorkerDefaults()\n    .Build();\n\nhost.Run();\n```\n\n```json\n\u002F\u002F host.json\n{\n  \"version\": \"2.0\",\n  \"logging\": {\n    \"applicationInsights\": {\n      \"samplingSettings\": {\n        \"isEnabled\": true\n      }\n    }\n  },\n  \"extensions\": {\n    \"http\": {\n      \"routePrefix\": \"\"\n    }\n  }\n}\n```\n\n```json\n\u002F\u002F local.settings.json\n{\n  \"IsEncrypted\": false,\n  \"Values\": {\n    \"AzureWebJobsStorage\": \"UseDevelopmentStorage=true\",\n    \"FUNCTIONS_WORKER_RUNTIME\": \"dotnet\"\n  }\n}\n```\n\n## Key Types Reference\n\n| Type | Purpose |\n|------|---------|\n| `WebJobsAuthenticationEventsTriggerAttribute` | Function trigger attribute |\n| `WebJobsTokenIssuanceStartRequest` | Token issuance event request |\n| `WebJobsTokenIssuanceStartResponse` | Token issuance event response |\n| `WebJobsProvideClaimsForToken` | Action to add claims |\n| `WebJobsAttributeCollectionStartRequest` | Attribute collection start request |\n| `WebJobsAttributeCollectionStartResponse` | Attribute collection start response |\n| `WebJobsAttributeCollectionSubmitRequest` | Attribute submission request |\n| `WebJobsAttributeCollectionSubmitResponse` | Attribute submission response |\n| `WebJobsSetPrefillValues` | Prefill form values |\n| `WebJobsShowBlockPage` | Block user with message |\n| `WebJobsShowValidationError` | Show validation errors |\n| `WebJobsModifyAttributeValues` | Modify submitted values |\n| `WebJobsOnOtpSendRequest` | OTP send event request |\n| `WebJobsOnOtpSendResponse` | OTP send event response |\n| `WebJobsOnOtpSendSuccess` | OTP sent successfully |\n| `WebJobsOnOtpSendFailed` | OTP send failed |\n| `WebJobsContinueWithDefaultBehavior` | Continue with default flow |\n\n## Entra ID Configuration\n\nAfter deploying your Function App, configure the custom extension in Entra ID:\n\n1. **Register the API** in Entra ID → App registrations\n2. **Create Custom Authentication Extension** in Entra ID → External Identities → Custom authentication extensions\n3. **Link to User Flow** in Entra ID → External Identities → User flows\n\n### Required App Registration Settings\n\n```\nExpose an API:\n  - Application ID URI: api:\u002F\u002F\u003Cyour-function-app-name>.azurewebsites.net\n  - Scope: CustomAuthenticationExtension.Receive.Payload\n\nAPI Permissions:\n  - Microsoft Graph: User.Read (delegated)\n```\n\n## Best Practices\n\n1. **Validate all inputs** — Never trust request data; validate before processing\n2. **Handle errors gracefully** — Return appropriate error responses\n3. **Log correlation IDs** — Use `CorrelationId` for troubleshooting\n4. **Keep functions fast** — Authentication events have timeout limits\n5. **Use managed identity** — Access Azure resources securely\n6. **Cache external data** — Avoid slow lookups on every request\n7. **Test locally** — Use Azure Functions Core Tools with sample payloads\n8. **Monitor with App Insights** — Track function execution and errors\n\n## Error Handling\n\n```csharp\n[FunctionName(\"OnTokenIssuanceStart\")]\npublic static WebJobsAuthenticationEventResponse Run(\n    [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,\n    ILogger log)\n{\n    try\n    {\n        \u002F\u002F Your logic here\n        var response = new WebJobsTokenIssuanceStartResponse();\n        response.Actions.Add(new WebJobsProvideClaimsForToken\n        {\n            Claims = new Dictionary\u003Cstring, string> { { \"claim\", \"value\" } }\n        });\n        return response;\n    }\n    catch (Exception ex)\n    {\n        log.LogError(ex, \"Error processing token issuance event\");\n        \n        \u002F\u002F Return empty response - authentication continues without custom claims\n        \u002F\u002F Do NOT throw - this would fail the authentication\n        return new WebJobsTokenIssuanceStartResponse();\n    }\n}\n```\n\n## Related SDKs\n\n| SDK | Purpose | Install |\n|-----|---------|---------|\n| `Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents` | Auth events (this SDK) | `dotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents` |\n| `Microsoft.Identity.Web` | Web app authentication | `dotnet add package Microsoft.Identity.Web` |\n| `Azure.Identity` | Azure authentication | `dotnet add package Azure.Identity` |\n\n## Reference Links\n\n| Resource | URL |\n|----------|-----|\n| NuGet Package | https:\u002F\u002Fwww.nuget.org\u002Fpackages\u002FMicrosoft.Azure.WebJobs.Extensions.AuthenticationEvents |\n| Custom Extensions Overview | https:\u002F\u002Flearn.microsoft.com\u002Fentra\u002Fidentity-platform\u002Fcustom-extension-overview |\n| Token Issuance Events | https:\u002F\u002Flearn.microsoft.com\u002Fentra\u002Fidentity-platform\u002Fcustom-extension-tokenissuancestart-setup |\n| Attribute Collection Events | https:\u002F\u002Flearn.microsoft.com\u002Fentra\u002Fidentity-platform\u002Fcustom-extension-attribute-collection |\n| GitHub Source | https:\u002F\u002Fgithub.com\u002FAzure\u002Fazure-sdk-for-net\u002Ftree\u002Fmain\u002Fsdk\u002Fentra\u002FMicrosoft.Azure.WebJobs.Extensions.AuthenticationEvents |\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n","# Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents (.NET)\n\nAzure Functions extension for handling Microsoft Entra ID custom authentication events.\n\n## Installation\n\n```bash\ndotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents\n```\n\n**Current Version**: v1.1.0 (stable)\n\n## Supported Events\n\n| Event | Purpose |\n|-------|---------|\n| `OnTokenIssuanceStart` | Add custom claims to tokens during issuance |\n| `OnAttributeCollectionStart` | Customize attribute collection UI before display |\n| `OnAttributeCollectionSubmit` | Validate\u002Fmodify attributes after user submission |\n| `OnOtpSend` | Custom OTP delivery (SMS, email, etc.) |\n\n## Core Workflows\n\n### 1. Token Enrichment (Add Custom Claims)\n\nAdd custom claims to access or ID tokens during sign-in.\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;\nusing Microsoft.Extensions.Logging;\n\npublic static class TokenEnrichmentFunction\n{\n    [FunctionName(\"OnTokenIssuanceStart\")]\n    public static WebJobsAuthenticationEventResponse Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,\n        ILogger log)\n    {\n        log.LogInformation(\"Token issuance event for user: {UserId}\", \n            request.Data?.AuthenticationContext?.User?.Id);\n\n        \u002F\u002F Create response with custom claims\n        var response = new WebJobsTokenIssuanceStartResponse();\n        \n        \u002F\u002F Add claims to the token\n        response.Actions.Add(new WebJobsProvideClaimsForToken\n        {\n            Claims = new Dictionary\u003Cstring, string>\n            {\n                { \"customClaim1\", \"customValue1\" },\n                { \"department\", \"Engineering\" },\n                { \"costCenter\", \"CC-12345\" },\n                { \"apiVersion\", \"v2\" }\n            }\n        });\n\n        return response;\n    }\n}\n```\n\n### 2. Token Enrichment with External Data\n\nFetch claims from external systems (databases, APIs).\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;\nusing Microsoft.Extensions.Logging;\nusing System.Net.Http;\nusing System.Text.Json;\n\npublic static class TokenEnrichmentWithExternalData\n{\n    private static readonly HttpClient _httpClient = new();\n\n    [FunctionName(\"OnTokenIssuanceStartExternal\")]\n    public static async Task\u003CWebJobsAuthenticationEventResponse> Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,\n        ILogger log)\n    {\n        string? userId = request.Data?.AuthenticationContext?.User?.Id;\n        \n        if (string.IsNullOrEmpty(userId))\n        {\n            log.LogWarning(\"No user ID in request\");\n            return new WebJobsTokenIssuanceStartResponse();\n        }\n\n        \u002F\u002F Fetch user data from external API\n        var userProfile = await GetUserProfileAsync(userId);\n        \n        var response = new WebJobsTokenIssuanceStartResponse();\n        response.Actions.Add(new WebJobsProvideClaimsForToken\n        {\n            Claims = new Dictionary\u003Cstring, string>\n            {\n                { \"employeeId\", userProfile.EmployeeId },\n                { \"department\", userProfile.Department },\n                { \"roles\", string.Join(\",\", userProfile.Roles) }\n            }\n        });\n\n        return response;\n    }\n\n    private static async Task\u003CUserProfile> GetUserProfileAsync(string userId)\n    {\n        var response = await _httpClient.GetAsync($\"https:\u002F\u002Fapi.example.com\u002Fusers\u002F{userId}\");\n        response.EnsureSuccessStatusCode();\n        var json = await response.Content.ReadAsStringAsync();\n        return JsonSerializer.Deserialize\u003CUserProfile>(json)!;\n    }\n}\n\npublic record UserProfile(string EmployeeId, string Department, string[] Roles);\n```\n\n### 3. Attribute Collection - Customize UI (Start Event)\n\nCustomize the attribute collection page before it's displayed.\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;\nusing Microsoft.Extensions.Logging;\n\npublic static class AttributeCollectionStartFunction\n{\n    [FunctionName(\"OnAttributeCollectionStart\")]\n    public static WebJobsAuthenticationEventResponse Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsAttributeCollectionStartRequest request,\n        ILogger log)\n    {\n        log.LogInformation(\"Attribute collection start for correlation: {CorrelationId}\",\n            request.Data?.AuthenticationContext?.CorrelationId);\n\n        var response = new WebJobsAttributeCollectionStartResponse();\n\n        \u002F\u002F Option 1: Continue with default behavior\n        response.Actions.Add(new WebJobsContinueWithDefaultBehavior());\n\n        \u002F\u002F Option 2: Prefill attributes\n        \u002F\u002F response.Actions.Add(new WebJobsSetPrefillValues\n        \u002F\u002F {\n        \u002F\u002F     Attributes = new Dictionary\u003Cstring, string>\n        \u002F\u002F     {\n        \u002F\u002F         { \"city\", \"Seattle\" },\n        \u002F\u002F         { \"country\", \"USA\" }\n        \u002F\u002F     }\n        \u002F\u002F });\n\n        \u002F\u002F Option 3: Show blocking page (prevent sign-up)\n        \u002F\u002F response.Actions.Add(new WebJobsShowBlockPage\n        \u002F\u002F {\n        \u002F\u002F     Message = \"Sign-up is currently disabled.\"\n        \u002F\u002F });\n\n        return response;\n    }\n}\n```\n\n### 4. Attribute Collection - Validate Submission (Submit Event)\n\nValidate and modify attributes after user submission.\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;\nusing Microsoft.Extensions.Logging;\n\npublic static class AttributeCollectionSubmitFunction\n{\n    [FunctionName(\"OnAttributeCollectionSubmit\")]\n    public static WebJobsAuthenticationEventResponse Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsAttributeCollectionSubmitRequest request,\n        ILogger log)\n    {\n        var response = new WebJobsAttributeCollectionSubmitResponse();\n\n        \u002F\u002F Access submitted attributes\n        var attributes = request.Data?.UserSignUpInfo?.Attributes;\n        \n        string? email = attributes?[\"email\"]?.ToString();\n        string? displayName = attributes?[\"displayName\"]?.ToString();\n\n        \u002F\u002F Validation example: block certain email domains\n        if (email?.EndsWith(\"@blocked.com\") == true)\n        {\n            response.Actions.Add(new WebJobsShowBlockPage\n            {\n                Message = \"Sign-up from this email domain is not allowed.\"\n            });\n            return response;\n        }\n\n        \u002F\u002F Validation example: show validation error\n        if (string.IsNullOrEmpty(displayName) || displayName.Length \u003C 3)\n        {\n            response.Actions.Add(new WebJobsShowValidationError\n            {\n                Message = \"Display name must be at least 3 characters.\",\n                AttributeErrors = new Dictionary\u003Cstring, string>\n                {\n                    { \"displayName\", \"Name is too short\" }\n                }\n            });\n            return response;\n        }\n\n        \u002F\u002F Modify attributes before saving\n        response.Actions.Add(new WebJobsModifyAttributeValues\n        {\n            Attributes = new Dictionary\u003Cstring, string>\n            {\n                { \"displayName\", displayName.Trim() },\n                { \"city\", attributes?[\"city\"]?.ToString()?.ToUpperInvariant() ?? \"\" }\n            }\n        });\n\n        return response;\n    }\n}\n```\n\n### 5. Custom OTP Delivery\n\nSend one-time passwords via custom channels (SMS, email, push notification).\n\n```csharp\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;\nusing Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;\nusing Microsoft.Extensions.Logging;\n\npublic static class CustomOtpFunction\n{\n    [FunctionName(\"OnOtpSend\")]\n    public static async Task\u003CWebJobsAuthenticationEventResponse> Run(\n        [WebJobsAuthenticationEventsTrigger] WebJobsOnOtpSendRequest request,\n        ILogger log)\n    {\n        var response = new WebJobsOnOtpSendResponse();\n\n        string? phoneNumber = request.Data?.OtpContext?.Identifier;\n        string? otp = request.Data?.OtpContext?.OneTimeCode;\n\n        if (string.IsNullOrEmpty(phoneNumber) || string.IsNullOrEmpty(otp))\n        {\n            log.LogError(\"Missing phone number or OTP\");\n            response.Actions.Add(new WebJobsOnOtpSendFailed\n            {\n                Error = \"Missing required data\"\n            });\n            return response;\n        }\n\n        try\n        {\n            \u002F\u002F Send OTP via your SMS provider\n            await SendSmsAsync(phoneNumber, $\"Your verification code is: {otp}\");\n            \n            response.Actions.Add(new WebJobsOnOtpSendSuccess());\n            log.LogInformation(\"OTP sent successfully to {PhoneNumber}\", phoneNumber);\n        }\n        catch (Exception ex)\n        {\n            log.LogError(ex, \"Failed to send OTP\");\n            response.Actions.Add(new WebJobsOnOtpSendFailed\n            {\n                Error = \"Failed to send verification code\"\n            });\n        }\n\n        return response;\n    }\n\n    private static async Task SendSmsAsync(string phoneNumber, string message)\n    {\n        \u002F\u002F Implement your SMS provider integration (Twilio, Azure Communication Services, etc.)\n        await Task.CompletedTask;\n    }\n}\n```\n\n### 6. Function App Configuration\n\nConfigure the Function App for authentication events.\n\n```csharp\n\u002F\u002F Program.cs (Isolated worker model)\nusing Microsoft.Extensions.Hosting;\n\nvar host = new HostBuilder()\n    .ConfigureFunctionsWorkerDefaults()\n    .Build();\n\nhost.Run();\n```\n\n```json\n\u002F\u002F host.json\n{\n  \"version\": \"2.0\",\n  \"logging\": {\n    \"applicationInsights\": {\n      \"samplingSettings\": {\n        \"isEnabled\": true\n      }\n    }\n  },\n  \"extensions\": {\n    \"http\": {\n      \"routePrefix\": \"\"\n    }\n  }\n}\n```\n\n```json\n\u002F\u002F local.settings.json\n{\n  \"IsEncrypted\": false,\n  \"Values\": {\n    \"AzureWebJobsStorage\": \"UseDevelopmentStorage=true\",\n    \"FUNCTIONS_WORKER_RUNTIME\": \"dotnet\"\n  }\n}\n```\n\n## Key Types Reference\n\n| Type | Purpose |\n|------|---------|\n| `WebJobsAuthenticationEventsTriggerAttribute` | Function trigger attribute |\n| `WebJobsTokenIssuanceStartRequest` | Token issuance event request |\n| `WebJobsTokenIssuanceStartResponse` | Token issuance event response |\n| `WebJobsProvideClaimsForToken` | Action to add claims |\n| `WebJobsAttributeCollectionStartRequest` | Attribute collection start request |\n| `WebJobsAttributeCollectionStartResponse` | Attribute collection start response |\n| `WebJobsAttributeCollectionSubmitRequest` | Attribute submission request |\n| `WebJobsAttributeCollectionSubmitResponse` | Attribute submission response |\n| `WebJobsSetPrefillValues` | Prefill form values |\n| `WebJobsShowBlockPage` | Block user with message |\n| `WebJobsShowValidationError` | Show validation errors |\n| `WebJobsModifyAttributeValues` | Modify submitted values |\n| `WebJobsOnOtpSendRequest` | OTP send event request |\n| `WebJobsOnOtpSendResponse` | OTP send event response |\n| `WebJobsOnOtpSendSuccess` | OTP sent successfully |\n| `WebJobsOnOtpSendFailed` | OTP send failed |\n| `WebJobsContinueWithDefaultBehavior` | Continue with default flow |\n\n## Entra ID Configuration\n\nAfter deploying your Function App, configure the custom extension in Entra ID:\n\n1. **Register the API** in Entra ID → App registrations\n2. **Create Custom Authentication Extension** in Entra ID → External Identities → Custom authentication extensions\n3. **Link to User Flow** in Entra ID → External Identities → User flows\n\n### Required App Registration Settings\n\n```\nExpose an API:\n  - Application ID URI: api:\u002F\u002F\u003Cyour-function-app-name>.azurewebsites.net\n  - Scope: CustomAuthenticationExtension.Receive.Payload\n\nAPI Permissions:\n  - Microsoft Graph: User.Read (delegated)\n```\n\n## Best Practices\n\n1. **Validate all inputs** — Never trust request data; validate before processing\n2. **Handle errors gracefully** — Return appropriate error responses\n3. **Log correlation IDs** — Use `CorrelationId` for troubleshooting\n4. **Keep functions fast** — Authentication events have timeout limits\n5. **Use managed identity** — Access Azure resources securely\n6. **Cache external data** — Avoid slow lookups on every request\n7. **Test locally** — Use Azure Functions Core Tools with sample payloads\n8. **Monitor with App Insights** — Track function execution and errors\n\n## Error Handling\n\n```csharp\n[FunctionName(\"OnTokenIssuanceStart\")]\npublic static WebJobsAuthenticationEventResponse Run(\n    [WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,\n    ILogger log)\n{\n    try\n    {\n        \u002F\u002F Your logic here\n        var response = new WebJobsTokenIssuanceStartResponse();\n        response.Actions.Add(new WebJobsProvideClaimsForToken\n        {\n            Claims = new Dictionary\u003Cstring, string> { { \"claim\", \"value\" } }\n        });\n        return response;\n    }\n    catch (Exception ex)\n    {\n        log.LogError(ex, \"Error processing token issuance event\");\n        \n        \u002F\u002F Return empty response - authentication continues without custom claims\n        \u002F\u002F Do NOT throw - this would fail the authentication\n        return new WebJobsTokenIssuanceStartResponse();\n    }\n}\n```\n\n## Related SDKs\n\n| SDK | Purpose | Install |\n|-----|---------|---------|\n| `Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents` | Auth events (this SDK) | `dotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents` |\n| `Microsoft.Identity.Web` | Web app authentication | `dotnet add package Microsoft.Identity.Web` |\n| `Azure.Identity` | Azure authentication | `dotnet add package Azure.Identity` |\n\n## Reference Links\n\n| Resource | URL |\n|----------|-----|\n| NuGet Package | https:\u002F\u002Fwww.nuget.org\u002Fpackages\u002FMicrosoft.Azure.WebJobs.Extensions.AuthenticationEvents |\n| Custom Extensions Overview | https:\u002F\u002Flearn.microsoft.com\u002Fentra\u002Fidentity-platform\u002Fcustom-extension-overview |\n| Token Issuance Events | https:\u002F\u002Flearn.microsoft.com\u002Fentra\u002Fidentity-platform\u002Fcustom-extension-tokenissuancestart-setup |\n| Attribute Collection Events | https:\u002F\u002Flearn.microsoft.com\u002Fentra\u002Fidentity-platform\u002Fcustom-extension-attribute-collection |\n| GitHub Source | https:\u002F\u002Fgithub.com\u002FAzure\u002Fazure-sdk-for-net\u002Ftree\u002Fmain\u002Fsdk\u002Fentra\u002FMicrosoft.Azure.WebJobs.Extensions.AuthenticationEvents |\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n",{"name":5,"risk":30,"source":33,"date_added":41,"description":7},"2026-02-27","main","7a38d8e7073e61c16eda85681cb32fb68a8657a5","skills-store\u002Fcopilot\u002Fmicrosoft-azure-webjobs-extensions-authentication-events-dotnet","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router","https:\u002F\u002Fgithub.com\u002Fsapsapshen","2026-04-14 21:12:12",{"source":8,"skillName":5,"commands":49,"prompt":59,"zipAvailable":60},[50,53,56],{"tool":51,"command":52},"npx","npx skills add sapsapshen\u002Fai-memory-router --skill microsoft-azure-webjobs-extensions-authentication-events-dotnet",{"tool":54,"command":55},"bunx","bunx skills add sapsapshen\u002Fai-memory-router --skill microsoft-azure-webjobs-extensions-authentication-events-dotnet",{"tool":57,"command":58},"pnpm","pnpm dlx skills add sapsapshen\u002Fai-memory-router --skill microsoft-azure-webjobs-extensions-authentication-events-dotnet","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcopilot\u002Fmicrosoft-azure-webjobs-extensions-authentication-events-dotnet\u002FSKILL.md",true,[],[63,87,113,123,136,155],{"skillNo":64,"name":65,"nameZh":6,"description":66,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":67,"categories":68,"keywords":74,"publishedAt":36},"01M71ATQRKG48SH06FQ697T5G9","wechat-search","Search WeChat Official Account articles using OpenClaw's web search and fetch capabilities with compliance-focused design.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fwechat-search-release\u002FSKILL.md",[69],{"categoryNo":70,"categoryName":71,"taxonomyType":72,"taxonomyTypeDesc":73},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[75,76,77,78,79,80,81,82,83,84,85,86],"wechat search","wechat","search","capabilities","openclaw","design","web search","compliance","web","official","fetch","with",{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":93,"publishedAt":36},"01M71ATNAVGZB35PFYM2GC5N00","trend-tap","Real-time trending topics aggregator across 7 platforms (X\u002FTwitter, Reddit, Google Trends, Hacker News, Zhihu, Bilibili, Weibo). Trigger: when user says 'trend', 'trends', 'trend tap', '热点', '热搜', or asks what is trending\u002Fhot. Do NOT use web_search — this skill already fetches live data from all platforms with zero API keys.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftrend-tap\u002FSKILL.md",[],[94,95,96,97,98,99,77,100,101,102,103,104,105,106,107,108,109,110,111,112],"trend","trend tap","tap","api keys","Bash","time","trending","hacker","aggregator","do","when","brew","live","data","google","install","read","across","api",{"skillNo":114,"name":115,"nameZh":6,"description":116,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":119,"publishedAt":36},"01M71ATJ9AM5KM15AA8PS302EB","trend-radar","Real-time trending topics aggregator across 7 platforms (X\u002FTwitter, Reddit, Google Trends, Hacker News, Zhihu, Bilibili, Weibo). Trigger: when user says 'trend', 'trends', 'trend pulse', '热点', '热搜', or asks what is trending\u002Fhot. Do NOT use web_search — this skill already fetches live data from all platforms with zero API keys.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftrend-radar\u002FSKILL.md",[],[120,121,94,105,106,107,108,98,77,110,122,111,112,104,102,101,100,109,99,97],"trend radar","radar","pulse",{"skillNo":124,"name":125,"nameZh":6,"description":126,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":127,"categories":128,"keywords":129,"publishedAt":36},"01M71ATGF095N3EJM9G8SD0BB5","tech-news-daily","每日科技新闻汇——从全球多个 RSS 源抓取最新科技新闻，翻译（英→中）、去重、排版成每日简报。当用户询问科技新闻、科技新鲜事、每日科技、技术动态、Daily Tech News 或类似表达时触发此技能。","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftech-news-daily\u002FSKILL.md",[],[130,131,132,133,134,135],"news","tech","tech news daily","daily","rss","翻译",{"skillNo":137,"name":138,"nameZh":6,"description":139,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":140,"categories":141,"keywords":147,"publishedAt":36},"01M71ATFAEAMQ17M70NQNY7SR8","summarize","Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube).","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fsummarize\u002FSKILL.md",[142],{"categoryNo":143,"categoryName":144,"taxonomyType":145,"taxonomyTypeDesc":146},"01JBSEEDCAT000000000000021","资料检索与研究","SCENARIO","使用场景",[138,83,148,149,150,86,105,151,152,153,96,154,109],"Audio","cli","youtube","images","https","files","pdfs",{"skillNo":156,"name":157,"nameZh":6,"description":158,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":159,"categories":160,"keywords":161,"publishedAt":36},"01M71ATDNS3R0M4H936PACQAWZ","sonoscli","Control Sonos speakers (discover\u002Fstatus\u002Fplay\u002Fvolume\u002Fgroup).","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fsonoscli\u002FSKILL.md",[],[157,162,163,164,109,165,152,166,167,168,169,170],"status","play","go","latest","github","discover","control","cmd","volume"]