feat: Add Official Microsoft & Gemini Skills (845+ Total)
🚀 Impact Significantly expands the capabilities of **Antigravity Awesome Skills** by integrating official skill collections from **Microsoft** and **Google Gemini**. This update increases the total skill count to **845+**, making the library even more comprehensive for AI coding assistants. ✨ Key Changes 1. New Official Skills - **Microsoft Skills**: Added a massive collection of official skills from [microsoft/skills](https://github.com/microsoft/skills). - Includes Azure, .NET, Python, TypeScript, and Semantic Kernel skills. - Preserves the original directory structure under `skills/official/microsoft/`. - Includes plugin skills from the `.github/plugins` directory. - **Gemini Skills**: Added official Gemini API development skills under `skills/gemini-api-dev/`. 2. New Scripts & Tooling - **`scripts/sync_microsoft_skills.py`**: A robust synchronization script that: - Clones the official Microsoft repository. - Preserves the original directory heirarchy. - Handles symlinks and plugin locations. - Generates attribution metadata. - **`scripts/tests/inspect_microsoft_repo.py`**: Debug tool to inspect the remote repository structure. - **`scripts/tests/test_comprehensive_coverage.py`**: Verification script to ensure 100% of skills are captured during sync. 3. Core Improvements - **`scripts/generate_index.py`**: Enhanced frontmatter parsing to safely handle unquoted values containing `@` symbols and commas (fixing issues with some Microsoft skill descriptions). - **`package.json`**: Added `sync:microsoft` and `sync:all-official` scripts for easy maintenance. 4. Documentation - Updated `README.md` to reflect the new skill counts (845+) and added Microsoft/Gemini to the provider list. - Updated `CATALOG.md` and `skills_index.json` with the new skills. 🧪 Verification - Ran `scripts/tests/test_comprehensive_coverage.py` to verify all Microsoft skills are detected. - Validated `generate_index.py` fixes by successfully indexing the new skills.
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
---
|
||||
name: microsoft-azure-webjobs-extensions-authentication-events-dotnet
|
||||
description: |
|
||||
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".
|
||||
---
|
||||
|
||||
# Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents (.NET)
|
||||
|
||||
Azure Functions extension for handling Microsoft Entra ID custom authentication events.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
dotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents
|
||||
```
|
||||
|
||||
**Current Version**: v1.1.0 (stable)
|
||||
|
||||
## Supported Events
|
||||
|
||||
| Event | Purpose |
|
||||
|-------|---------|
|
||||
| `OnTokenIssuanceStart` | Add custom claims to tokens during issuance |
|
||||
| `OnAttributeCollectionStart` | Customize attribute collection UI before display |
|
||||
| `OnAttributeCollectionSubmit` | Validate/modify attributes after user submission |
|
||||
| `OnOtpSend` | Custom OTP delivery (SMS, email, etc.) |
|
||||
|
||||
## Core Workflows
|
||||
|
||||
### 1. Token Enrichment (Add Custom Claims)
|
||||
|
||||
Add custom claims to access or ID tokens during sign-in.
|
||||
|
||||
```csharp
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
public static class TokenEnrichmentFunction
|
||||
{
|
||||
[FunctionName("OnTokenIssuanceStart")]
|
||||
public static WebJobsAuthenticationEventResponse Run(
|
||||
[WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation("Token issuance event for user: {UserId}",
|
||||
request.Data?.AuthenticationContext?.User?.Id);
|
||||
|
||||
// Create response with custom claims
|
||||
var response = new WebJobsTokenIssuanceStartResponse();
|
||||
|
||||
// Add claims to the token
|
||||
response.Actions.Add(new WebJobsProvideClaimsForToken
|
||||
{
|
||||
Claims = new Dictionary<string, string>
|
||||
{
|
||||
{ "customClaim1", "customValue1" },
|
||||
{ "department", "Engineering" },
|
||||
{ "costCenter", "CC-12345" },
|
||||
{ "apiVersion", "v2" }
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Token Enrichment with External Data
|
||||
|
||||
Fetch claims from external systems (databases, APIs).
|
||||
|
||||
```csharp
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
|
||||
public static class TokenEnrichmentWithExternalData
|
||||
{
|
||||
private static readonly HttpClient _httpClient = new();
|
||||
|
||||
[FunctionName("OnTokenIssuanceStartExternal")]
|
||||
public static async Task<WebJobsAuthenticationEventResponse> Run(
|
||||
[WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,
|
||||
ILogger log)
|
||||
{
|
||||
string? userId = request.Data?.AuthenticationContext?.User?.Id;
|
||||
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
log.LogWarning("No user ID in request");
|
||||
return new WebJobsTokenIssuanceStartResponse();
|
||||
}
|
||||
|
||||
// Fetch user data from external API
|
||||
var userProfile = await GetUserProfileAsync(userId);
|
||||
|
||||
var response = new WebJobsTokenIssuanceStartResponse();
|
||||
response.Actions.Add(new WebJobsProvideClaimsForToken
|
||||
{
|
||||
Claims = new Dictionary<string, string>
|
||||
{
|
||||
{ "employeeId", userProfile.EmployeeId },
|
||||
{ "department", userProfile.Department },
|
||||
{ "roles", string.Join(",", userProfile.Roles) }
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static async Task<UserProfile> GetUserProfileAsync(string userId)
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"https://api.example.com/users/{userId}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<UserProfile>(json)!;
|
||||
}
|
||||
}
|
||||
|
||||
public record UserProfile(string EmployeeId, string Department, string[] Roles);
|
||||
```
|
||||
|
||||
### 3. Attribute Collection - Customize UI (Start Event)
|
||||
|
||||
Customize the attribute collection page before it's displayed.
|
||||
|
||||
```csharp
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
public static class AttributeCollectionStartFunction
|
||||
{
|
||||
[FunctionName("OnAttributeCollectionStart")]
|
||||
public static WebJobsAuthenticationEventResponse Run(
|
||||
[WebJobsAuthenticationEventsTrigger] WebJobsAttributeCollectionStartRequest request,
|
||||
ILogger log)
|
||||
{
|
||||
log.LogInformation("Attribute collection start for correlation: {CorrelationId}",
|
||||
request.Data?.AuthenticationContext?.CorrelationId);
|
||||
|
||||
var response = new WebJobsAttributeCollectionStartResponse();
|
||||
|
||||
// Option 1: Continue with default behavior
|
||||
response.Actions.Add(new WebJobsContinueWithDefaultBehavior());
|
||||
|
||||
// Option 2: Prefill attributes
|
||||
// response.Actions.Add(new WebJobsSetPrefillValues
|
||||
// {
|
||||
// Attributes = new Dictionary<string, string>
|
||||
// {
|
||||
// { "city", "Seattle" },
|
||||
// { "country", "USA" }
|
||||
// }
|
||||
// });
|
||||
|
||||
// Option 3: Show blocking page (prevent sign-up)
|
||||
// response.Actions.Add(new WebJobsShowBlockPage
|
||||
// {
|
||||
// Message = "Sign-up is currently disabled."
|
||||
// });
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Attribute Collection - Validate Submission (Submit Event)
|
||||
|
||||
Validate and modify attributes after user submission.
|
||||
|
||||
```csharp
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
public static class AttributeCollectionSubmitFunction
|
||||
{
|
||||
[FunctionName("OnAttributeCollectionSubmit")]
|
||||
public static WebJobsAuthenticationEventResponse Run(
|
||||
[WebJobsAuthenticationEventsTrigger] WebJobsAttributeCollectionSubmitRequest request,
|
||||
ILogger log)
|
||||
{
|
||||
var response = new WebJobsAttributeCollectionSubmitResponse();
|
||||
|
||||
// Access submitted attributes
|
||||
var attributes = request.Data?.UserSignUpInfo?.Attributes;
|
||||
|
||||
string? email = attributes?["email"]?.ToString();
|
||||
string? displayName = attributes?["displayName"]?.ToString();
|
||||
|
||||
// Validation example: block certain email domains
|
||||
if (email?.EndsWith("@blocked.com") == true)
|
||||
{
|
||||
response.Actions.Add(new WebJobsShowBlockPage
|
||||
{
|
||||
Message = "Sign-up from this email domain is not allowed."
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// Validation example: show validation error
|
||||
if (string.IsNullOrEmpty(displayName) || displayName.Length < 3)
|
||||
{
|
||||
response.Actions.Add(new WebJobsShowValidationError
|
||||
{
|
||||
Message = "Display name must be at least 3 characters.",
|
||||
AttributeErrors = new Dictionary<string, string>
|
||||
{
|
||||
{ "displayName", "Name is too short" }
|
||||
}
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// Modify attributes before saving
|
||||
response.Actions.Add(new WebJobsModifyAttributeValues
|
||||
{
|
||||
Attributes = new Dictionary<string, string>
|
||||
{
|
||||
{ "displayName", displayName.Trim() },
|
||||
{ "city", attributes?["city"]?.ToString()?.ToUpperInvariant() ?? "" }
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Custom OTP Delivery
|
||||
|
||||
Send one-time passwords via custom channels (SMS, email, push notification).
|
||||
|
||||
```csharp
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents;
|
||||
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
public static class CustomOtpFunction
|
||||
{
|
||||
[FunctionName("OnOtpSend")]
|
||||
public static async Task<WebJobsAuthenticationEventResponse> Run(
|
||||
[WebJobsAuthenticationEventsTrigger] WebJobsOnOtpSendRequest request,
|
||||
ILogger log)
|
||||
{
|
||||
var response = new WebJobsOnOtpSendResponse();
|
||||
|
||||
string? phoneNumber = request.Data?.OtpContext?.Identifier;
|
||||
string? otp = request.Data?.OtpContext?.OneTimeCode;
|
||||
|
||||
if (string.IsNullOrEmpty(phoneNumber) || string.IsNullOrEmpty(otp))
|
||||
{
|
||||
log.LogError("Missing phone number or OTP");
|
||||
response.Actions.Add(new WebJobsOnOtpSendFailed
|
||||
{
|
||||
Error = "Missing required data"
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send OTP via your SMS provider
|
||||
await SendSmsAsync(phoneNumber, $"Your verification code is: {otp}");
|
||||
|
||||
response.Actions.Add(new WebJobsOnOtpSendSuccess());
|
||||
log.LogInformation("OTP sent successfully to {PhoneNumber}", phoneNumber);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogError(ex, "Failed to send OTP");
|
||||
response.Actions.Add(new WebJobsOnOtpSendFailed
|
||||
{
|
||||
Error = "Failed to send verification code"
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static async Task SendSmsAsync(string phoneNumber, string message)
|
||||
{
|
||||
// Implement your SMS provider integration (Twilio, Azure Communication Services, etc.)
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Function App Configuration
|
||||
|
||||
Configure the Function App for authentication events.
|
||||
|
||||
```csharp
|
||||
// Program.cs (Isolated worker model)
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var host = new HostBuilder()
|
||||
.ConfigureFunctionsWorkerDefaults()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
```
|
||||
|
||||
```json
|
||||
// host.json
|
||||
{
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"applicationInsights": {
|
||||
"samplingSettings": {
|
||||
"isEnabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"http": {
|
||||
"routePrefix": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// local.settings.json
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Key Types Reference
|
||||
|
||||
| Type | Purpose |
|
||||
|------|---------|
|
||||
| `WebJobsAuthenticationEventsTriggerAttribute` | Function trigger attribute |
|
||||
| `WebJobsTokenIssuanceStartRequest` | Token issuance event request |
|
||||
| `WebJobsTokenIssuanceStartResponse` | Token issuance event response |
|
||||
| `WebJobsProvideClaimsForToken` | Action to add claims |
|
||||
| `WebJobsAttributeCollectionStartRequest` | Attribute collection start request |
|
||||
| `WebJobsAttributeCollectionStartResponse` | Attribute collection start response |
|
||||
| `WebJobsAttributeCollectionSubmitRequest` | Attribute submission request |
|
||||
| `WebJobsAttributeCollectionSubmitResponse` | Attribute submission response |
|
||||
| `WebJobsSetPrefillValues` | Prefill form values |
|
||||
| `WebJobsShowBlockPage` | Block user with message |
|
||||
| `WebJobsShowValidationError` | Show validation errors |
|
||||
| `WebJobsModifyAttributeValues` | Modify submitted values |
|
||||
| `WebJobsOnOtpSendRequest` | OTP send event request |
|
||||
| `WebJobsOnOtpSendResponse` | OTP send event response |
|
||||
| `WebJobsOnOtpSendSuccess` | OTP sent successfully |
|
||||
| `WebJobsOnOtpSendFailed` | OTP send failed |
|
||||
| `WebJobsContinueWithDefaultBehavior` | Continue with default flow |
|
||||
|
||||
## Entra ID Configuration
|
||||
|
||||
After deploying your Function App, configure the custom extension in Entra ID:
|
||||
|
||||
1. **Register the API** in Entra ID → App registrations
|
||||
2. **Create Custom Authentication Extension** in Entra ID → External Identities → Custom authentication extensions
|
||||
3. **Link to User Flow** in Entra ID → External Identities → User flows
|
||||
|
||||
### Required App Registration Settings
|
||||
|
||||
```
|
||||
Expose an API:
|
||||
- Application ID URI: api://<your-function-app-name>.azurewebsites.net
|
||||
- Scope: CustomAuthenticationExtension.Receive.Payload
|
||||
|
||||
API Permissions:
|
||||
- Microsoft Graph: User.Read (delegated)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Validate all inputs** — Never trust request data; validate before processing
|
||||
2. **Handle errors gracefully** — Return appropriate error responses
|
||||
3. **Log correlation IDs** — Use `CorrelationId` for troubleshooting
|
||||
4. **Keep functions fast** — Authentication events have timeout limits
|
||||
5. **Use managed identity** — Access Azure resources securely
|
||||
6. **Cache external data** — Avoid slow lookups on every request
|
||||
7. **Test locally** — Use Azure Functions Core Tools with sample payloads
|
||||
8. **Monitor with App Insights** — Track function execution and errors
|
||||
|
||||
## Error Handling
|
||||
|
||||
```csharp
|
||||
[FunctionName("OnTokenIssuanceStart")]
|
||||
public static WebJobsAuthenticationEventResponse Run(
|
||||
[WebJobsAuthenticationEventsTrigger] WebJobsTokenIssuanceStartRequest request,
|
||||
ILogger log)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Your logic here
|
||||
var response = new WebJobsTokenIssuanceStartResponse();
|
||||
response.Actions.Add(new WebJobsProvideClaimsForToken
|
||||
{
|
||||
Claims = new Dictionary<string, string> { { "claim", "value" } }
|
||||
});
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogError(ex, "Error processing token issuance event");
|
||||
|
||||
// Return empty response - authentication continues without custom claims
|
||||
// Do NOT throw - this would fail the authentication
|
||||
return new WebJobsTokenIssuanceStartResponse();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Related SDKs
|
||||
|
||||
| SDK | Purpose | Install |
|
||||
|-----|---------|---------|
|
||||
| `Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents` | Auth events (this SDK) | `dotnet add package Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents` |
|
||||
| `Microsoft.Identity.Web` | Web app authentication | `dotnet add package Microsoft.Identity.Web` |
|
||||
| `Azure.Identity` | Azure authentication | `dotnet add package Azure.Identity` |
|
||||
|
||||
## Reference Links
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| NuGet Package | https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents |
|
||||
| Custom Extensions Overview | https://learn.microsoft.com/entra/identity-platform/custom-extension-overview |
|
||||
| Token Issuance Events | https://learn.microsoft.com/entra/identity-platform/custom-extension-tokenissuancestart-setup |
|
||||
| Attribute Collection Events | https://learn.microsoft.com/entra/identity-platform/custom-extension-attribute-collection |
|
||||
| GitHub Source | https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents |
|
||||
Reference in New Issue
Block a user