Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.0 Beta 1 #11

Merged
merged 12 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/Build_&_Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache .nuke/temp, ~/.nuget/packages
- name: 'Cache: .nuke/temp, ~/.nuget/packages'
uses: actions/cache@v3
with:
path: |
.nuke/temp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
- name: Run './build.cmd Test'
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
- name: 'Run: Test'
run: ./build.cmd Test
env:
FgaStoreId: ${{ secrets.FGA_STORE_ID }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/Manual_Nuget_Push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache .nuke/temp, ~/.nuget/packages
- name: 'Cache: .nuke/temp, ~/.nuget/packages'
uses: actions/cache@v3
with:
path: |
.nuke/temp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
- name: Run './build.cmd NugetPush'
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
- name: 'Run: NugetPush'
run: ./build.cmd NugetPush
env:
NugetApiKey: ${{ secrets.NUGET_API_KEY }}
4 changes: 2 additions & 2 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Build Schema",
"$ref": "#/definitions/build",
"title": "Build Schema",
"definitions": {
"build": {
"type": "object",
Expand Down Expand Up @@ -125,4 +125,4 @@
}
}
}
}
}
4 changes: 2 additions & 2 deletions Package.Build.props
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<Project>
<PropertyGroup>
<Version>0.9.0-alpha</Version>
<Version>1.0.0-beta.1</Version>
<Authors>Hawxy</Authors>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/Hawxy/Fga.Net</PackageProjectUrl>
<RepositoryUrl>https://github.com/Hawxy/Fga.Net</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Copyright>Hawxy 2022</Copyright>
<Copyright>Hawxy 2022-2023</Copyright>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
Expand Down
108 changes: 82 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
[![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Fga.Net.DependencyInjection?label=Fga.Net.DependencyInjection&style=flat-square)](https://www.nuget.org/packages/Fga.Net.DependencyInjection)
[![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Fga.Net.AspNetCore?label=Fga.Net.AspNetCore&style=flat-square)](https://www.nuget.org/packages/Fga.Net.AspNetCore)

#### Note: This project is in its early stages and will have breaking changes as FGA matures.
#### Note: This project is currently in beta. Breaking changes may occur before release.

### Packages
**`Fga.Net.DependencyInjection`**: Provides dependency injection/configuration extensions for [OpenFga.Sdk](https://github.com/openfga/dotnet-sdk)

**`Fga.Net.AspNetCore`**: Includes Authorization middleware to support FGA checks as part of a request's lifecycle.
**`Fga.Net.AspNetCore`**: Authorization middleware to perform FGA checks for inbound requests.

## Getting Started

This package is compatible with the OSS OpenFGA as well as the managed Auth0 FGA service.
This package is compatible with the OSS OpenFGA as well as the managed Auth0 FGA service. Usage of DSL v1.1 is required.

Please ensure you have a basic understanding of how FGA works before continuing: [OpenFGA Docs](https://openfga.dev/) or [Auth0 FGA Docs](https://docs.fga.dev/)

Expand All @@ -26,42 +26,63 @@ Install `Fga.Net.AspNetCore` from Nuget before continuing.

Ensure you have a Store ID, Client ID, and Client Secret ready from [How to get your API keys](https://docs.fga.dev/integration/getting-your-api-keys).


1. Add your `StoreId`, `ClientId` and `ClientSecret` to your application configuration, ideally via the [dotnet secrets manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows#enable-secret-storage).
2. Add the following code to your ASP.NET Core services configuration:
```cs
builder.Services.AddOpenFgaClient(x =>
builder.Services.AddOpenFgaClient(config =>
{
x.WithAuth0FgaDefaults(builder.Configuration["Auth0Fga:ClientId"], builder.Configuration["Auth0Fga:ClientSecret"]);
config.ConfigureAuth0Fga(x =>
{
x.WithAuthentication(builder.Configuration["Auth0Fga:ClientId"]!, builder.Configuration["Auth0Fga:ClientSecret"]!);
});

x.StoreId = builder.Configuration["Auth0Fga:StoreId"];
config.SetStoreId(builder.Configuration["Auth0Fga:StoreId"]!);
});

builder.Services.AddOpenFgaMiddleware();
```

The `WithAuth0FgaDefaults` extension will configure the relevant OpenFGA client settings to work with Auth0 FGA's US environment.
The `ConfigureAuth0Fga` extension will configure the client to work with the Auth0 US environment. An environment selector will be added as additional regions come online.

### OpenFGA

OpenFGA configuration is very similar to the [SDK Setup Guide](https://openfga.dev/docs/getting-started/setup-sdk-client)

1. Add the FGA `ApiScheme`, `ApiHost` & `StoreId` to your application configuration.
2. Add the following code to your ASP.NET Core configuration:
```cs
builder.Services.AddOpenFgaClient(x =>
services.AddOpenFgaClient(config =>
{
x.ApiScheme = builder.Configuration["Fga:ApiScheme"];
x.ApiHost = builder.Configuration["Fga:ApiHost"];
x.StoreId = builder.Configuration["Fga:StoreId"];
config.ConfigureOpenFga(x =>
{
x.SetConnection(context.Configuration["Fga:ApiScheme"] context.Configuration["Fga:ApiHost"]);
});
config.SetStoreId(context.Configuration["Fga:StoreId"]);
});

builder.Services.AddOpenFgaMiddleware();
```

Authentication can be added to OpenFGA connections via the relevant extensions:

```csharp
config.ConfigureOpenFga(x =>
{
x.SetConnection(Uri.UriSchemeHttp, context.Configuration["Fga:ApiHost"]);

// Add API key auth
x.WithApiKeyAuthentication(context.Configuration["Fga:ApiKey"]);
// or OIDC auth
x.WithOidcAuthentication(
context.Configuration["Fga:ClientId"],
context.Configuration["Fga:ClientSecret"],
context.Configuration["Fga:Issuer"],
context.Configuration["Fga:Audience"]);
});

```

### Authorization Policy Setup

We'll need to setup our authorization policy like so:
Your authorization policy should be configured with `RequireAuthenticatedUser` and `AddFgaRequirement` at minimum:

```cs
builder.Services.AddAuthorization(options =>
Expand All @@ -73,6 +94,8 @@ builder.Services.AddAuthorization(options =>
});
```

A constant authorization key is included for convenience, but `AddFgaRequirement` can be used with any additional policy as required.

### Built-in Check Attributes

`Fga.Net.AspNetCore` ships with a number of attributes that should cover the most common authorization sources for FGA checks:
Expand All @@ -82,14 +105,14 @@ builder.Services.AddAuthorization(options =>
- `FgaQueryObjectAttribute` - Computes the Object via a value in the query string
- `FgaRouteObjectAttribute` - Computes the Object via a value in the routes path

If you want to use these attributes, you need to configure how the user's identity is resolved from the `ClaimsPrincipal`.
If you want to use these attributes, you need to configure how the user's identifier is constructed from the users claims.
The example below uses the Name, which is mapped to the User ID in a default Auth0 integration.

```cs
builder.Services.AddOpenFgaMiddleware(config =>
{
//DSL v1.1 requires the user type to be included
config.UserIdentityResolver = principal => $"user:{principal.Identity!.Name!}";
//'user' should be the name of the user type that you're using within your FGA model
config.SetUserIdentifier("user", principal => principal.Identity!.Name!);
});
```

Expand Down Expand Up @@ -155,6 +178,23 @@ An additional pre-made attribute that allows all tuple values to be hardcoded st

This package registers both the `OpenFgaApi` and `OpenFgaClient` types in the DI container. `OpenFgaClient` is a higher level abstraction and preferred over `OpenFgaApi` for general use.

## Testing

When running tests against your API or service collection, you likely want a different client configuration than usual. You can achieve this by calling `PostConfigureFgaClient` on your services configuration:

```cs
// Replaces existing configuration
services.PostConfigureFgaClient(config =>
{
config.SetStoreId(storeId);
config.ConfigureOpenFga(x =>
{
x.SetConnection(Uri.UriSchemeHttp, openFgaUrl);
});
});

```

## Worker Service / Generic Host Setup

`Fga.Net.DependencyInjection` ships with the `AddOpenFgaClient` service collection extension that handles all required wire-up.
Expand All @@ -169,16 +209,32 @@ To get started:
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
// Auth0 FGA
services.AddOpenFgaClient(config =>
{
config.ConfigureAuth0Fga(x =>
{
x.WithAuthentication(context.Configuration["Auth0Fga:ClientId"], context.Configuration["Auth0Fga:ClientSecret"]);
});
config.SetStoreId(context.Configuration["Auth0Fga:StoreId"]);
});

// OpenFGA
services.AddOpenFgaClient(config =>
{
// Auth0 FGA
config.WithAuth0FgaDefaults(context.Configuration["Auth0Fga:ClientId"], context.Configuration["Auth0Fga:ClientSecret"]);
config.StoreId = context.Configuration["Auth0Fga:StoreId"];

// OpenFGA
config.ApiScheme = context.Configuration["Fga:ApiScheme"];
config.ApiHost = context.Configuration["Fga:ApiHost"];
config.StoreId = context.Configuration["Fga:StoreId"];
config.ConfigureOpenFga(x =>
{
x.SetConnection(Uri.UriSchemeHttp, context.Configuration["Fga:ApiHost"]);

// Optionally add authentication settings
x.WithApiKeyAuthentication(context.Configuration["Fga:ApiKey"]);
x.WithOidcAuthentication(
context.Configuration["Fga:ClientId"],
context.Configuration["Fga:ClientSecret"],
context.Configuration["Fga:Issuer"],
context.Configuration["Fga:Audience"]);
});
config.SetStoreId(context.Configuration["Fga:StoreId"]);
});

services.AddHostedService<MyBackgroundWorker>();
Expand Down
3 changes: 2 additions & 1 deletion build/Build.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using Nuke.Common;
using Nuke.Common.CI;
using Nuke.Common.CI.GitHubActions;
Expand Down Expand Up @@ -40,7 +41,7 @@ class Build : NukeBuild
.Before(Restore)
.Executes(() =>
{
EnsureCleanDirectory(ArtifactsDirectory);
ArtifactsDirectory.CreateOrCleanDirectory();
});

Target Restore => _ => _
Expand Down
2 changes: 1 addition & 1 deletion build/_build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Nuke.Common" Version="6.3.0" />
<PackageReference Include="Nuke.Common" Version="7.0.2" />
</ItemGroup>

</Project>
27 changes: 15 additions & 12 deletions samples/Fga.Example.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System.Security.Claims;
using Fga.Example.AspNetCore;
using Fga.Net.AspNetCore;
using Fga.Net.AspNetCore.Authorization;
using Fga.Net.AspNetCore.Authorization.Attributes;
using Fga.Net.DependencyInjection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
Expand Down Expand Up @@ -30,25 +28,30 @@


// Auth0 FGA
builder.Services.AddOpenFgaClient(clientConfig =>
builder.Services.AddOpenFgaClient(config =>
{
clientConfig.WithAuth0FgaDefaults(builder.Configuration["Auth0Fga:ClientId"]!,
builder.Configuration["Auth0Fga:ClientSecret"]!);
clientConfig.StoreId = builder.Configuration["Auth0Fga:StoreId"];
config.ConfigureAuth0Fga(x =>
{
x.WithAuthentication(builder.Configuration["Auth0Fga:ClientId"]!, builder.Configuration["Auth0Fga:ClientSecret"]!);
});

config.SetStoreId(builder.Configuration["Auth0Fga:StoreId"]!);
});

// OpenFGA
/*builder.Services.AddOpenFgaClient(x =>
/* OpenFGA
builder.Services.AddOpenFgaClient(x =>
{
x.ApiScheme = builder.Configuration["Fga:ApiScheme"];
x.ApiHost = builder.Configuration["Fga:ApiHost"];
x.StoreId = builder.Configuration["Fga:StoreId"];
x.ConfigureOpenFga(x =>
{
x.SetConnection(builder.Configuration["Fga:ApiScheme"]!, builder.Configuration["Fga:ApiHost"]!);
});

x.SetStoreId(builder.Configuration["Fga:StoreId"]);
});*/

builder.Services.AddOpenFgaMiddleware(middlewareConfig =>
{
middlewareConfig.UserIdentityResolver = principal => $"user:{principal.Identity!.Name!}";
middlewareConfig.SetUserIdentifier("user", principal => principal.Identity!.Name!);
});

builder.Services.AddAuthorization(options =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@

namespace Fga.Example.AspNetCore.TestControllers;

public class TestAuthorizationAttribute : FgaAttribute
public class TestAuthorizationAttribute : FgaBaseObjectAttribute
{

public override ValueTask<string> GetUser(HttpContext context)
{
return ValueTask.FromResult(context.User.Identity!.Name!);
}

public override ValueTask<string> GetRelation(HttpContext context)
{
return ValueTask.FromResult("fake-relation");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Fga.Net.AspNetCore.Authorization;
using Fga.Net.AspNetCore.Authorization.Attributes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand Down
32 changes: 24 additions & 8 deletions samples/Fga.Example.GenericHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,32 @@
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
// Auth0 FGA
services.AddOpenFgaClient(config =>
{
// Auth0 FGA
config.WithAuth0FgaDefaults(context.Configuration["Auth0Fga:ClientId"], context.Configuration["Auth0Fga:ClientSecret"]);
config.StoreId = context.Configuration["Auth0Fga:StoreId"];

// OpenFGA
config.ApiScheme = context.Configuration["Fga:ApiScheme"];
config.ApiHost = context.Configuration["Fga:ApiHost"];
config.StoreId = context.Configuration["Fga:StoreId"];
config.ConfigureAuth0Fga(x =>
{
x.WithAuthentication(context.Configuration["Auth0Fga:ClientId"], context.Configuration["Auth0Fga:ClientSecret"]);
});
config.SetStoreId(context.Configuration["Auth0Fga:StoreId"]);
});

// OpenFGA
services.AddOpenFgaClient(config =>
{
config.ConfigureOpenFga(x =>
{
x.SetConnection(Uri.UriSchemeHttp, context.Configuration["Fga:ApiHost"]);

// Optionally add authentication settings
x.WithApiKeyAuthentication(context.Configuration["Fga:ApiKey"]);
x.WithOidcAuthentication(
context.Configuration["Fga:ClientId"],
context.Configuration["Fga:ClientSecret"],
context.Configuration["Fga:Issuer"],
context.Configuration["Fga:Audience"]);
});
config.SetStoreId(context.Configuration["Fga:StoreId"]);
});

services.AddHostedService<MyBackgroundWorker>();
Expand Down
Loading