diff --git a/Functions-Authorize.sln b/Functions-Authorize.sln index 226756e..e252eae 100644 --- a/Functions-Authorize.sln +++ b/Functions-Authorize.sln @@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Isolated.Tests", "test\Isol EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProc.Tests", "test\InProc.Tests\InProc.Tests.csproj", "{3E24DA59-3292-430E-B784-5FC13CAEDA5E}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SampleIsolatedFunctionsFSharp_V4", "sample\SampleIsolatedFunctionsFSharp.V4\SampleIsolatedFunctionsFSharp_V4.fsproj", "{60017B6F-8423-4D2F-A080-9C7A40C663C3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -95,6 +97,10 @@ Global {3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Release|Any CPU.Build.0 = Release|Any CPU + {60017B6F-8423-4D2F-A080-9C7A40C663C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60017B6F-8423-4D2F-A080-9C7A40C663C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60017B6F-8423-4D2F-A080-9C7A40C663C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60017B6F-8423-4D2F-A080-9C7A40C663C3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -109,6 +115,7 @@ Global {E7143566-8DA8-4D2F-AA23-7FB0BADE229F} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB} {4E74E715-7549-46C4-AF0F-464C24FA00E7} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB} {3E24DA59-3292-430E-B784-5FC13CAEDA5E} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB} + {60017B6F-8423-4D2F-A080-9C7A40C663C3} = {53EC585B-CE9B-4E7D-B2E2-F7A9B6DA0FE7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {546E3A6C-060C-4630-BEEE-46A1F8715347} diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/.vscode/extensions.json b/sample/SampleIsolatedFunctionsFSharp.V4/.vscode/extensions.json new file mode 100644 index 0000000..dde673d --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions" + ] +} \ No newline at end of file diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/HelperFunctions.fs b/sample/SampleIsolatedFunctionsFSharp.V4/HelperFunctions.fs new file mode 100644 index 0000000..ce4e2da --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/HelperFunctions.fs @@ -0,0 +1,43 @@ +namespace SampleInProcFunctions.V4 + +open System.Security.Claims +open Common.Tests +open Microsoft.AspNetCore.Http +open Microsoft.AspNetCore.Mvc +open Microsoft.Azure.Functions.Worker +open Microsoft.Extensions.Logging +open System + +type HelperFunctions() = + + [] + member _.Run( + [] + req: HttpRequest, + log: ILogger) = + task { + + let firstName = "Test" + let lastName = "User" + let email = "test.user@domain.com" + let token = JwtUtils.GenerateJwtToken( + [ + new Claim("aud", "api://default") + new Claim("iss", "https://localhost/jwt/") + new Claim("scp", "user_impersonation") + new Claim("tid", Guid.NewGuid().ToString()) + new Claim("oid", Guid.NewGuid().ToString()) + new Claim("name", $"{firstName} {lastName}") + new Claim(ClaimTypes.Name, email) + new Claim(ClaimTypes.Upn, email) + new Claim(ClaimTypes.Email, email) + new Claim(ClaimTypes.GivenName, firstName) + new Claim(ClaimTypes.Surname, lastName) + new Claim("role", "Just a user") + new Claim("role", "admin") + ]) + + return OkObjectResult(token) + } + + diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/Program.fs b/sample/SampleIsolatedFunctionsFSharp.V4/Program.fs new file mode 100644 index 0000000..905196d --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/Program.fs @@ -0,0 +1,59 @@ +module Program + +open Common.Tests +open DarkLoop.Azure.Functions.Authorization +open Microsoft.Azure.Functions.Worker +open Microsoft.Extensions.DependencyInjection +open Microsoft.Extensions.Hosting +open Microsoft.IdentityModel.Tokens + +// IMPORTANT: because local.settings.json is not included in the repository, you must create it manually +// If you don't create it. the isolated function will not run. Ensure that the file has the following content: +// +// { +// "IsEncrypted": false, +// "Values": { +// "AzureWebJobsStorage": "UseDevelopmentStorage=true", +// "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" +// } +// } + +let host = + HostBuilder() + .ConfigureFunctionsWebApplication(fun builder -> + //This is needed to make F# variants of startup work nicely + FunctionsAuthorizationExtensionStartup().Configure(builder) + builder.UseFunctionsAuthorization() |> ignore ) + .ConfigureServices(fun services -> + services + .AddFunctionsAuthentication(JwtFunctionsBearerDefaults.AuthenticationScheme) + .AddJwtFunctionsBearer(fun options -> + // this line is here to bypass the token validation + // and test the functionality of this library. + // you can create a dummy token by executing the GetTestToken function in HelperFunctions.cs + // THE FOLLOWING LINE SHOULD BE REMOVED IN A REAL-WORLD SCENARIO + options.SecurityTokenValidators.Add(TestTokenValidator()) + + // this is what you should look for in a real-world scenario + // comment the lines if you cloned this repository and want to test the library + options.Authority <- "https://login.microsoftonline.com/" + options.Audience <- "" + options.TokenValidationParameters <- TokenValidationParameters + ( + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true + ) + () + ) |> ignore + + services + .AddFunctionsAuthorization(fun options -> + // Add your policies here + () + ) |> ignore + ) + .Build() + +host.Run() diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/Properties/serviceDependencies.json b/sample/SampleIsolatedFunctionsFSharp.V4/Properties/serviceDependencies.json new file mode 100644 index 0000000..df4dcc9 --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/Properties/serviceDependencies.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + }, + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/Properties/serviceDependencies.local.json b/sample/SampleIsolatedFunctionsFSharp.V4/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..b804a28 --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/Properties/serviceDependencies.local.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + }, + "storage1": { + "type": "storage.emulator", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/SampleIsolatedFunctionsFSharp_V4.fsproj b/sample/SampleIsolatedFunctionsFSharp.V4/SampleIsolatedFunctionsFSharp_V4.fsproj new file mode 100644 index 0000000..91a59a2 --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/SampleIsolatedFunctionsFSharp_V4.fsproj @@ -0,0 +1,47 @@ + + + net8.0 + v4 + Exe + 17c2def3-36ba-461c-8cf2-2305557bb98b + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + + + \ No newline at end of file diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/TestFunction.fs b/sample/SampleIsolatedFunctionsFSharp.V4/TestFunction.fs new file mode 100644 index 0000000..4a4d9bc --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/TestFunction.fs @@ -0,0 +1,43 @@ +namespace SampleIsolatedFunctionsFSharp.V4 + +open System.Text +open DarkLoop.Azure.Functions.Authorization +open Microsoft.AspNetCore.Authentication +open Microsoft.AspNetCore.Authorization +open Microsoft.AspNetCore.Http +open Microsoft.AspNetCore.Mvc +open Microsoft.Azure.Functions.Worker +open Microsoft.Extensions.DependencyInjection +open Microsoft.Extensions.Logging + + +[] +type TestFunction(logger:ILogger) = + let _logger = logger + + [] + [] + member _.Run([] req:HttpRequest) = + task { + _logger.LogInformation("F# HTTP trigger function processed a request.") + + let provider = req.HttpContext.RequestServices + let schProvider = provider.GetService() + + let sb = new StringBuilder() + sb.AppendLine("Authentication schemes:") |> ignore + + if (schProvider <> null) then + let! allScheme = schProvider.GetAllSchemesAsync() + for scheme in allScheme do + sb.AppendLine($" {scheme.Name} -> {scheme.HandlerType}") |> ignore + + + sb.AppendLine()|> ignore + sb.AppendLine($"User:")|> ignore + sb.AppendLine($" Name -> {req.HttpContext.User.Identity.Name}")|> ignore + let email = req.HttpContext.User.FindFirst("email")|> Option.ofObj|>Option.map _.Value + sb.AppendLine($" Email -> {email}")|> ignore + + return OkObjectResult(sb.ToString()) + } \ No newline at end of file diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/host.json b/sample/SampleIsolatedFunctionsFSharp.V4/host.json new file mode 100644 index 0000000..5df170b --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/host.json @@ -0,0 +1,12 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + } +} \ No newline at end of file diff --git a/sample/SampleIsolatedFunctionsFSharp.V4/local.settings.json b/sample/SampleIsolatedFunctionsFSharp.V4/local.settings.json new file mode 100644 index 0000000..64a872f --- /dev/null +++ b/sample/SampleIsolatedFunctionsFSharp.V4/local.settings.json @@ -0,0 +1,7 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" + } +} \ No newline at end of file diff --git a/src/isolated/README.md b/src/isolated/README.md index 493a986..7e3b6e0 100644 --- a/src/isolated/README.md +++ b/src/isolated/README.md @@ -67,6 +67,8 @@ adds the `"FunctionsBearer"` scheme. Clients still submit token for Authorizatio Notice the call to `UseFunctionsAuthorization` in the `ConfigureFunctionsWebAppliction` method. This is required to ensure that the middleware is placed in the pipeline where required function information is available.` +Mind that the startup if coding in F# will be somewhat different. Please do check the [sample for F#](../../sample/SampleIsolatedFunctionsFSharp.V4/Program.fs) + ### Using the attribute And now lets use `FunctionAuthorizeAttribute` the same way we use `AuthorizeAttribute` in our ASP.NET Core applications. ```csharp