Skip to content
Draft
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
10 changes: 9 additions & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
"version": "5.1.0",
"commands": [
"dotnet-cake"
]
],
"rollForward": false
},
"docfx": {
"version": "2.78.4",
"commands": [
"docfx"
],
"rollForward": false
}
}
}
59 changes: 59 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Documentation

on:
push:
branches: [ main ]
paths:
- 'docs/**'
- 'src/**/*.cs'
- '.github/workflows/docs.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

- name: Setup Pages
uses: actions/configure-pages@v5

- name: Restore tools
run: dotnet tool restore

- name: Restore dependencies
run: dotnet restore

- name: Build documentation
run: dotnet docfx docs/docfx.json

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/_site

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -442,3 +442,14 @@ $RECYCLE.BIN/
!.vscode/extensions.json

appsettings.local*.json

##
## DocFX
##
# DocFX build output
docs/_site/
docs/api/
docs/obj/

# DocFX log files
docs/*.log
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Common Development Commands

### Build and Test
- **Build the solution**: `./build.sh` (Mac/Linux) or use Cake directly: `dotnet cake`
- **Build the solution**: `dotnet build`
- **Run unit tests**: `dotnet test test/TrueLayer.Tests/TrueLayer.Tests.csproj`
- **Run acceptance tests**: `dotnet test test/TrueLayer.AcceptanceTests/TrueLayer.AcceptanceTests.csproj`
- **Run specific test**: `dotnet test --filter "TestMethodName"`
Expand Down Expand Up @@ -60,7 +60,7 @@ Uses Cake build system (`build.cake`) with tasks for:
- NuGet package publishing
- CI/CD integration with GitHub Actions
### Code Style
- C# 10.0 language features
- C# 12.0 language features
- Nullable reference types enabled
- Code style enforcement via `EnforceCodeStyleInBuild`
- EditorConfig and analyzer rules applied
Expand Down
8 changes: 1 addition & 7 deletions build.cake
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Install .NET Core Global tools.
#tool "dotnet:?package=dotnet-reportgenerator-globaltool&version=5.4.9"
#tool "dotnet:?package=dotnet-reportgenerator-globaltool&version=5.4.18"
#tool "dotnet:?package=dotnet-sonarscanner&version=11.0.0"

#tool "dotnet:?package=dotnet-reportgenerator-globaltool&version=5.4.17"
#tool "dotnet:?package=coveralls.net&version=4.0.1"

// Install addins
#addin nuget:?package=Cake.Coverlet&version=5.1.1
#addin nuget:?package=Cake.Sonar&version=5.0.0
Expand Down Expand Up @@ -228,7 +225,6 @@ public static class BuildContext
public static bool IsTag { get; private set; }
public static string NugetApiUrl { get; private set; }
public static string NugetApiKey { get; private set; }
public static bool ForcePushDocs { get; private set; }

public static bool ShouldPublishToNuget
=> !string.IsNullOrWhiteSpace(BuildContext.NugetApiUrl) && !string.IsNullOrWhiteSpace(BuildContext.NugetApiKey);
Expand Down Expand Up @@ -256,8 +252,6 @@ public static class BuildContext
NugetApiUrl = context.EnvironmentVariable("NUGET_PRE_API_URL");
NugetApiKey = context.EnvironmentVariable("NUGET_PRE_API_KEY");
}

ForcePushDocs = context.Argument<bool>("force-docs", false);
}

public static void PrintParameters(ICakeContext context)
Expand Down
240 changes: 240 additions & 0 deletions docs/articles/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# Authentication

The TrueLayer .NET client handles authentication automatically using your Client ID and Secret. However, you can optimize performance using auth token caching strategies.

## Basic Configuration

Configure authentication in your `appsettings.json`:

```json
{
"TrueLayer": {
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"UseSandbox": true
}
}
```

## Token Caching Strategies

Auth tokens have a limited lifetime. Caching them reduces API calls and improves performance.

### In-Memory Caching

Recommended for most applications:

```csharp
services.AddTrueLayer(
configuration,
options => { /* config */ },
authTokenCachingStrategy: AuthTokenCachingStrategies.InMemory
);
```

### Custom Caching

Implement `IAuthTokenCache` for distributed caching:

```csharp
public class RedisAuthTokenCache : IAuthTokenCache
{
private readonly IDistributedCache _cache;

public RedisAuthTokenCache(IDistributedCache cache)
{
_cache = cache;
}

public async Task<string?> GetAsync(string key, CancellationToken ct = default)
{
return await _cache.GetStringAsync(key, ct);
}

public async Task SetAsync(
string key,
string value,
TimeSpan expiry,
CancellationToken ct = default)
{
await _cache.SetStringAsync(
key,
value,
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = expiry },
ct
);
}
}

// Register
services.AddSingleton<IAuthTokenCache, RedisAuthTokenCache>();
services.AddTrueLayer(
configuration,
options => { /* config */ },
authTokenCachingStrategy: AuthTokenCachingStrategies.Custom
);
```

## Environment Configuration

### Sandbox vs Production

Control which environment to use:

```csharp
// Use sandbox
"TrueLayer": {
"UseSandbox": true
}

// Use production
"TrueLayer": {
"UseSandbox": false
}
```

### Custom API URIs

Override default endpoints:

```csharp
services.AddTrueLayer(configuration, options =>
{
options.Auth = new ApiOptions
{
Uri = new Uri("https://custom-auth.truelayer.com")
};
});
```

## Signing Keys for Payments

Payment requests must be cryptographically signed:

```csharp
services.AddTrueLayer(configuration, options =>
{
if (options.Payments?.SigningKey != null)
{
// Load from secure storage (e.g., Azure Key Vault, AWS Secrets Manager)
options.Payments.SigningKey.PrivateKey = await secretManager.GetSecretAsync("truelayer-private-key");
}
});
```

> [!WARNING]
> Never hardcode private keys or commit them to source control.

## Multiple Clients

Configure multiple clients with different credentials:

```csharp
services
.AddKeyedTrueLayer("GBP", configuration, options =>
{
options.ClientId = "gbp-client-id";
options.ClientSecret = "gbp-secret";
})
.AddKeyedTrueLayer("EUR", configuration, options =>
{
options.ClientId = "eur-client-id";
options.ClientSecret = "eur-secret";
});
```

Usage:

```csharp
public class PaymentService
{
private readonly ITrueLayerClient _gbpClient;
private readonly ITrueLayerClient _eurClient;

public PaymentService(
[FromKeyedServices("GBP")] ITrueLayerClient gbpClient,
[FromKeyedServices("EUR")] ITrueLayerClient eurClient)
{
_gbpClient = gbpClient;
_eurClient = eurClient;
}
}
```

## Security Best Practices

### 1. Secure Credential Storage

Use secret management services:

```csharp
// Azure Key Vault
var keyVault = new SecretClient(
new Uri("https://your-keyvault.vault.azure.net/"),
new DefaultAzureCredential()
);

var clientSecret = await keyVault.GetSecretAsync("TrueLayer-ClientSecret");
var privateKey = await keyVault.GetSecretAsync("TrueLayer-PrivateKey");

services.AddTrueLayer(configuration, options =>
{
options.ClientSecret = clientSecret.Value.Value;
if (options.Payments?.SigningKey != null)
{
options.Payments.SigningKey.PrivateKey = privateKey.Value.Value;
}
});
```

### 2. Rotate Credentials Regularly

Implement credential rotation:

```csharp
services.AddOptions<TrueLayerOptions>()
.Configure<ISecretManager>((options, secretManager) =>
{
// Reload credentials periodically
options.ClientSecret = secretManager.GetSecret("ClientSecret");
});
```

### 3. Limit Token Lifetime

Configure shorter cache durations for sensitive environments:

```csharp
services.AddSingleton<IAuthTokenCache>(sp =>
new CustomAuthTokenCache(maxLifetime: TimeSpan.FromMinutes(30))
);
```

### 4. Use HTTPS Only

Ensure all traffic is encrypted (default behavior).

## Troubleshooting

### 401 Unauthorized

- Verify Client ID and Secret are correct
- Check credentials are for the correct environment (sandbox/production)
- Ensure credentials haven't expired

### 403 Forbidden

- Verify your application has the required API permissions in TrueLayer Console
- Check signing key is uploaded and Key ID is correct

### Token Cache Issues

- Clear cache and retry
- Verify cache implementation is working correctly
- Check cache expiry times

## See Also

- [Installation](installation.md)
- [Multiple Clients](multiple-clients.md)
- [Configuration Reference](configuration.md)
Loading