Browse Source

Fix interactive login on SQL databases by widening the token type

OpenIddict 7.4 persists the full token type identifiers instead of the
short names used by earlier versions. The longest of them,
"urn:openiddict:params:oauth:token-type:authorization_code", needs 57
characters, but the token type column only allows 50, because the pinned
Squidex.OpenIdDict.EntityFramework fork still declares the old length.

Every interactive login therefore failed when the authorization code was
stored, and the user was redirected to the error page:

    Msg 2628: String or binary data would be truncated in table
    'OpenIddictTokens', column 'Type'.

The client credentials flow was unaffected, because the access token
identifier still fits into 50 characters. MongoDB was unaffected as
well, because it does not enforce a length.

Widen the column to 150 characters, which is the length official
OpenIddict uses for the same reason, and add a migration for all three
relational providers. The new test compares the stored value instead of
only saving it, because MySql truncates silently outside of strict mode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pull/1329/head
Joe de Ronde 4 weeks ago
parent
commit
8621906513
  1. 14
      backend/src/Squidex.Data.EntityFramework/AppDbContext.cs
  2. 1628
      backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260811133803_WidenOpenIddictTokenType.Designer.cs
  3. 44
      backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260811133803_WidenOpenIddictTokenType.cs
  4. 6
      backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/MySqlDbContextModelSnapshot.cs
  5. 1629
      backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260811133716_WidenOpenIddictTokenType.Designer.cs
  6. 40
      backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260811133716_WidenOpenIddictTokenType.cs
  7. 6
      backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/PostgresDbContextModelSnapshot.cs
  8. 1631
      backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260811133719_WidenOpenIddictTokenType.Designer.cs
  9. 40
      backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260811133719_WidenOpenIddictTokenType.cs
  10. 6
      backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/SqlServerDbContextModelSnapshot.cs
  11. 35
      backend/tests/Squidex.Data.Tests/EntityFramework/Domain/Users/EFOpenIddictTests.cs

14
backend/src/Squidex.Data.EntityFramework/AppDbContext.cs

@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using OpenIddict.EntityFrameworkCore.Models;
using Squidex.Assets.TusAdapter;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
@ -54,6 +55,7 @@ public abstract class AppDbContext(DbContextOptions options, IJsonSerializer jso
builder.UseMigration();
builder.UseNames(jsonSerializer, jsonColumnType);
builder.UseOpenIddict();
builder.UseOpenIddictTokenType();
builder.UseRequest(jsonSerializer, jsonColumnType);
builder.UseRules(jsonSerializer, jsonColumnType);
builder.UseSchema(jsonSerializer, jsonColumnType);
@ -73,6 +75,18 @@ public abstract class AppDbContext(DbContextOptions options, IJsonSerializer jso
internal static class Extensions
#pragma warning restore MA0048 // File name must match type name
{
// The pinned Squidex.OpenIdDict.EntityFramework fork still limits the token type to 50 characters,
// but OpenIddict 7.4 persists the full token type identifiers instead of the short names it used
// before. The longest of them, "urn:openiddict:params:oauth:token-type:authorization_code", needs
// 57 characters, so every interactive login failed to store its authorization code. Official
// OpenIddict widened the column to 150 characters for the same reason, so use the same length here.
public static void UseOpenIddictTokenType(this ModelBuilder builder)
{
builder.Entity<OpenIddictEntityFrameworkCoreToken>()
.Property(x => x.Type)
.HasMaxLength(150);
}
public static void UseIdentity(this ModelBuilder builder, IJsonSerializer jsonSerializer, string? jsonColumn)
{
builder.UseSnapshot<DefaultKeyStore.State>(jsonSerializer, jsonColumn);

1628
backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260811133803_WidenOpenIddictTokenType.Designer.cs

File diff suppressed because it is too large

44
backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260811133803_WidenOpenIddictTokenType.cs

@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Squidex.Providers.MySql.App.Migrations
{
/// <inheritdoc />
public partial class WidenOpenIddictTokenType : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Type",
table: "OpenIddictTokens",
type: "varchar(150)",
maxLength: 150,
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(50)",
oldMaxLength: 50,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Type",
table: "OpenIddictTokens",
type: "varchar(50)",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(150)",
oldMaxLength: 150,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}

6
backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/MySqlDbContextModelSnapshot.cs

@ -18,7 +18,7 @@ namespace Squidex.Providers.MySql.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.5")
.HasAnnotation("ProductVersion", "10.0.6")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
@ -306,8 +306,8 @@ namespace Squidex.Providers.MySql.Migrations
.HasColumnType("varchar(400)");
b.Property<string>("Type")
.HasMaxLength(50)
.HasColumnType("varchar(50)");
.HasMaxLength(150)
.HasColumnType("varchar(150)");
b.HasKey("Id");

1629
backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260811133716_WidenOpenIddictTokenType.Designer.cs

File diff suppressed because it is too large

40
backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260811133716_WidenOpenIddictTokenType.cs

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Squidex.Providers.Postgres.App.Migrations
{
/// <inheritdoc />
public partial class WidenOpenIddictTokenType : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Type",
table: "OpenIddictTokens",
type: "character varying(150)",
maxLength: 150,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(50)",
oldMaxLength: 50,
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Type",
table: "OpenIddictTokens",
type: "character varying(50)",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(150)",
oldMaxLength: 150,
oldNullable: true);
}
}
}

6
backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/PostgresDbContextModelSnapshot.cs

@ -18,7 +18,7 @@ namespace Squidex.Providers.Postgres.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.5")
.HasAnnotation("ProductVersion", "10.0.6")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
@ -307,8 +307,8 @@ namespace Squidex.Providers.Postgres.Migrations
.HasColumnType("character varying(400)");
b.Property<string>("Type")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.HasKey("Id");

1631
backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260811133719_WidenOpenIddictTokenType.Designer.cs

File diff suppressed because it is too large

40
backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260811133719_WidenOpenIddictTokenType.cs

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Squidex.Providers.SqlServer.App.Migrations
{
/// <inheritdoc />
public partial class WidenOpenIddictTokenType : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Type",
table: "OpenIddictTokens",
type: "nvarchar(150)",
maxLength: 150,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(50)",
oldMaxLength: 50,
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Type",
table: "OpenIddictTokens",
type: "nvarchar(50)",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(150)",
oldMaxLength: 150,
oldNullable: true);
}
}
}

6
backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/SqlServerDbContextModelSnapshot.cs

@ -18,7 +18,7 @@ namespace Squidex.Providers.SqlServer.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.5")
.HasAnnotation("ProductVersion", "10.0.6")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@ -308,8 +308,8 @@ namespace Squidex.Providers.SqlServer.Migrations
.HasColumnType("nvarchar(400)");
b.Property<string>("Type")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.HasKey("Id");

35
backend/tests/Squidex.Data.Tests/EntityFramework/Domain/Users/EFOpenIddictTests.cs

@ -48,4 +48,39 @@ public abstract class EFOpenIddictTests<TContext>(ISqlFixture<TContext> fixture)
await dbContext.SaveChangesAsync();
}
[Fact]
public async Task Should_roundtrip_openiddict_token_with_long_type_identifier()
{
// OpenIddict persists the full token type identifier, and the longest of them needs 57
// characters, which is more than the 50 characters the token type column used to allow.
var type = TokenTypeIdentifiers.Private.AuthorizationCode;
var tokenId = Guid.NewGuid().ToString();
await using (var dbContext = await fixture.DbContextFactory.CreateDbContextAsync())
{
var token = new OpenIddictEntityFrameworkCoreToken
{
Id = tokenId,
ApplicationId = null,
CreationDate = DateTime.UtcNow,
ExpirationDate = DateTime.UtcNow.AddMinutes(5),
Status = Statuses.Valid,
Subject = "admin@squidex.io",
Type = type,
};
dbContext.Set<OpenIddictEntityFrameworkCoreToken>().Add(token);
await dbContext.SaveChangesAsync();
}
await using (var dbContext = await fixture.DbContextFactory.CreateDbContextAsync())
{
// Compare the stored value, because not every database fails when it truncates.
var found = await dbContext.Set<OpenIddictEntityFrameworkCoreToken>().FindAsync(tokenId);
Assert.Equal(type, found?.Type);
}
}
}

Loading…
Cancel
Save