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

Add support for backchannel logout #125

Merged
merged 3 commits into from
Dec 5, 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
78 changes: 78 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Organizations](#organizations)
- [Extra parameters](#extra-parameters)
- [Roles](#roles)
- [Backchannel Logout](#backchannel-logout)
- [Blazor Server](#blazor-server)

## Login and Logout
Expand Down Expand Up @@ -313,6 +314,83 @@ public IActionResult Admin()
}
```

## Backchannel Logout

Backchannel logout can be configured by calling `WithBackchannelLogout()` when calling `AddAuth0WebAppAuthentication`.

```csharp
services
.AddAuth0WebAppAuthentication(PlaygroundConstants.AuthenticationScheme, options =>
{
options.Domain = Configuration["Auth0:Domain"];
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
}).WithBackchannelLogout();

```

Additionally, you will also need to call `UseBackchannelLogout();` on the ApplicationBuilder:

```csharp
app.UseBackchannelLogout();
```

As logout tokens need to be stored, you will also need to provide something for our SDK to store the tokens in.

```csharp
services.AddTransient<ILogoutTokenHandler, CustomLogoutTokenHandler>();
```

The implementation of `CustomLogoutTokenHandler` will heaviliy depend on your situation, but here's a blueprint you can use:

```csharp
public class CustomLogoutTokenHandler : ILogoutTokenHandler
{
public CustomLogoutTokenHandler()
{
}

public Task OnTokenReceivedAsync(string issuer, string sid, string logoutToken, TimeSpan expiration)
{
// When a token is received, you need to store it for the duration of `expiration`, using `issuer` and `sid` as the identifiers.
}

public Task<bool> IsLoggedOutAsync(string issuer, string sid)
{
// Return a boolean based on whether or not you find a logout token using the `issuer` and `sid`.
}
}
```

### Distributed caching
If you want to connect the backchannel logout to a [distributed cache](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed), such as redis, to store the logout tokens, you can use:

```csharp
public class CustomDistributedLogoutTokenHandler : ILogoutTokenHandler
{
private readonly IDistributedCache _cache;

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

public async Task OnTokenReceivedAsync(string issuer, string sid, string logoutToken, TimeSpan expiration)
{
await _cache.SetAsync($"{issuer}|{sid}", Encoding.ASCII.GetBytes(logoutToken), new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration
});
}

public async Task<bool> IsLoggedOutAsync(string issuer, string sid)
{
var token = await _cache.GetAsync($"{issuer}|{sid}");
return token != null;
}
}
```

## Blazor Server

The `Auth0-AspNetCore-Authentication` SDK works with Blazor Server in an almost identical way as how it's integrated in ASP.NET Core MVC.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.Threading.Tasks;
using Auth0.AspNetCore.Authentication.BackchannelLogout;

namespace Auth0.AspNetCore.Authentication.Playground
{
public class CustomClearSessionLogoutTokenHandler : ILogoutTokenHandler
{
private readonly ITicketStore store;

public CustomClearSessionLogoutTokenHandler(ITicketStore store)
{
this.store = store;
}

public async Task OnTokenReceivedAsync(string issuer, string sid, string logoutToken, TimeSpan expiration)
{
await store.RemoveAsync(sid);
}

public Task<bool> IsLoggedOutAsync(string issuer, string sid)
{
return Task.FromResult(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;

namespace Auth0.AspNetCore.Authentication.Playground;

public class CustomDistributedCache : IDistributedCache
{
private Dictionary<string, byte[]> _store = new Dictionary<string, byte[]>();

public byte[]? Get(string key)

Check warning on line 13 in playground/Auth0.AspNetCore.Authentication.Playground/CustomDistributedCache.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
return this._store.GetValueOrDefault(key);
}

public Task<byte[]?> GetAsync(string key, CancellationToken token = default)

Check warning on line 18 in playground/Auth0.AspNetCore.Authentication.Playground/CustomDistributedCache.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
return Task.FromResult(this._store.GetValueOrDefault(key));
}

public void Refresh(string key)
{
throw new NotImplementedException();
}

public Task RefreshAsync(string key, CancellationToken token = default)
{
throw new NotImplementedException();
}

public void Remove(string key)
{
this._store.Remove(key);
}

public Task RemoveAsync(string key, CancellationToken token = default)
{
this._store.Remove(key);
return Task.CompletedTask;
}

public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
this._store.Add(key, value);
}

public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
{
this._store.Add(key, value);
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using Microsoft.Extensions.Caching.Distributed;
using System.Text;
using System.Threading.Tasks;
using Auth0.AspNetCore.Authentication.BackchannelLogout;

namespace Auth0.AspNetCore.Authentication.Playground
{
public class CustomDistributedLogoutTokenHandler : ILogoutTokenHandler
{
private readonly IDistributedCache _cache;

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

public async Task OnTokenReceivedAsync(string issuer, string sid, string logoutToken, TimeSpan expiration)
{
await _cache.SetAsync($"{issuer}|{sid}", Encoding.ASCII.GetBytes(logoutToken), new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration
});
}

public async Task<bool> IsLoggedOutAsync(string issuer, string sid)
{
var token = await _cache.GetAsync($"{issuer}|{sid}");
return token != null;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Memory;
using System.Linq;
using System.Threading.Tasks;

namespace Auth0.AspNetCore.Authentication.Playground
{
public class CustomInMemoryTicketStore : ITicketStore
{
private readonly IMemoryCache _cache;

public CustomInMemoryTicketStore(IMemoryCache cache)
{
_cache = cache;
}

public Task RemoveAsync(string key)
{
_cache.Remove(key);

return Task.CompletedTask;
}

public Task<AuthenticationTicket> RetrieveAsync(string key)
{
var ticket = _cache.Get<AuthenticationTicket>(key);

return Task.FromResult(ticket);
}

public Task RenewAsync(string key, AuthenticationTicket ticket)
{
_cache.Set(key, ticket);

return Task.CompletedTask;
}

public Task<string> StoreAsync(AuthenticationTicket ticket)
{
var key = ticket.Principal.Claims
.First(c => c.Type == "sid").Value;

_cache.Set(key, ticket);

return Task.FromResult(key);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using Microsoft.Extensions.Caching.Memory;
using System.Threading.Tasks;
using Auth0.AspNetCore.Authentication.BackchannelLogout;

namespace Auth0.AspNetCore.Authentication.Playground
{
public class CustomLogoutTokenHandler : ILogoutTokenHandler
{
private readonly IMemoryCache _memoryCache;

public CustomLogoutTokenHandler(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}

public Task OnTokenReceivedAsync(string issuer, string sid, string logoutToken, TimeSpan expiration)
{
_memoryCache.Set($"{issuer}|{sid}", logoutToken, expiration);

return Task.CompletedTask;
}

public Task<bool> IsLoggedOutAsync(string issuer, string sid)
{
var token = _memoryCache.Get($"{issuer}|{sid}");
return Task.FromResult(token != null);
}
}

}
Loading