Skip to content

Instantly share code, notes, and snippets.

@gterdem
Last active May 10, 2023 17:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gterdem/6417a914722e715b9d7fb28e75eeee51 to your computer and use it in GitHub Desktop.
Save gterdem/6417a914722e715b9d7fb28e75eeee51 to your computer and use it in GitHub Desktop.
OpenIddictDataSeeder updated to OpenIddict for microservice template
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using OpenIddict.Abstractions;
using Volo.Abp;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.PermissionManagement;
using Volo.Abp.Uow;
namespace Acme.BookStore.IdentityService.DbMigrations;
public class IdentityServerDataSeeder : IDataSeedContributor, ITransientDependency
{
private readonly IConfiguration _configuration;
private readonly ICurrentTenant _currentTenant;
private readonly IOpenIddictApplicationManager _applicationManager;
private readonly IOpenIddictScopeManager _scopeManager;
private readonly IPermissionDataSeeder _permissionDataSeeder;
private readonly IStringLocalizer<OpenIddictResponse> L;
public IdentityServerDataSeeder(
IConfiguration configuration,
ICurrentTenant currentTenant,
IOpenIddictApplicationManager applicationManager,
IOpenIddictScopeManager scopeManager,
IPermissionDataSeeder permissionDataSeeder,
IStringLocalizer<OpenIddictResponse> l)
{
_configuration = configuration;
_currentTenant = currentTenant;
_applicationManager = applicationManager;
_scopeManager = scopeManager;
_permissionDataSeeder = permissionDataSeeder;
L = l;
}
public Task SeedAsync(DataSeedContext context)
{
return SeedAsync();
}
[UnitOfWork]
public virtual async Task SeedAsync()
{
using (_currentTenant.Change(null))
{
await CreateApiScopesAsync();
await CreateWebGatewaySwaggerClientsAsync();
await CreateClientsAsync();
}
}
private async Task CreateApiScopesAsync()
{
await CreateScopesAsync("AccountService");
await CreateScopesAsync("IdentityService");
await CreateScopesAsync("AdministrationService");
await CreateScopesAsync("SaasService");
await CreateScopesAsync("ProductService");
}
private async Task CreateWebGatewaySwaggerClientsAsync()
{
await CreateSwaggerClientAsync("WebGateway", new[] { "AccountService", "IdentityService", "AdministrationService", "SaasService", "ProductService" });
}
private async Task CreateSwaggerClientAsync(string name, string[] scopes = null)
{
var commonScopes = new List<string>
{
OpenIddictConstants.Permissions.Scopes.Address,
OpenIddictConstants.Permissions.Scopes.Email,
OpenIddictConstants.Permissions.Scopes.Phone,
OpenIddictConstants.Permissions.Scopes.Profile,
OpenIddictConstants.Permissions.Scopes.Roles
};
scopes ??= new[] { name };
// Swagger Client
var swaggerClientId = $"{name}_Swagger";
if (!swaggerClientId.IsNullOrWhiteSpace())
{
var webGatewaySwaggerRootUrl = _configuration[$"OpenIddict:Applications:{name}:RootUrl"].TrimEnd('/');
var publicWebGatewayRootUrl = _configuration[$"OpenIddict:Applications:PublicWebGateway:RootUrl"].TrimEnd('/');
var accountServiceRootUrl = _configuration[$"OpenIddict:Resources:AccountService:RootUrl"].TrimEnd('/');
var identityServiceRootUrl = _configuration[$"OpenIddict:Resources:IdentityService:RootUrl"].TrimEnd('/');
var administrationServiceRootUrl = _configuration[$"OpenIddict:Resources:AdministrationService:RootUrl"].TrimEnd('/');
var saasServiceRootUrl = _configuration[$"OpenIddict:Resources:SaasService:RootUrl"].TrimEnd('/');
var productServiceRootUrl = _configuration[$"OpenIddict:Resources:ProductService:RootUrl"].TrimEnd('/');
await CreateApplicationAsync(
name: swaggerClientId,
type: OpenIddictConstants.ClientTypes.Public,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Swagger Client",
secret: null,
grantTypes: new List<string>
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
},
scopes: commonScopes.Union(scopes).ToList(),
redirectUris: new List<string> {
$"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html", // WebGateway redirect uri
$"{publicWebGatewayRootUrl}/swagger/oauth2-redirect.html", // PublicWebGateway redirect uri
$"{accountServiceRootUrl}/swagger/oauth2-redirect.html", // AccountService redirect uri
$"{identityServiceRootUrl}/swagger/oauth2-redirect.html", // IdentityService redirect uri
$"{administrationServiceRootUrl}/swagger/oauth2-redirect.html", // AdministrationService redirect uri
$"{saasServiceRootUrl}/swagger/oauth2-redirect.html", // SaasService redirect uri
$"{productServiceRootUrl}/swagger/oauth2-redirect.html", // ProductService redirect uri
}
);
}
}
private async Task CreateScopesAsync(string name)
{
if (await _scopeManager.FindByNameAsync(name) == null)
{
await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor
{
Name = name,
DisplayName = name + " API",
Resources =
{
name
}
});
}
}
private async Task CreateClientsAsync()
{
var commonScopes = new List<string>
{
OpenIddictConstants.Permissions.Scopes.Address,
OpenIddictConstants.Permissions.Scopes.Email,
OpenIddictConstants.Permissions.Scopes.Phone,
OpenIddictConstants.Permissions.Scopes.Profile,
OpenIddictConstants.Permissions.Scopes.Roles
};
//Web Client
var webClientRootUrl = _configuration["OpenIddict:Applications:BookStore_Web:RootUrl"].EnsureEndsWith('/');
await CreateApplicationAsync(
name: "BookStore_Web",
type: OpenIddictConstants.ClientTypes.Confidential,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Web Client",
secret: "1q2w3e*",
grantTypes: new List<string> //Hybrid flow
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.Implicit
},
scopes: commonScopes.Union(new[] {"AccountService", "IdentityService", "AdministrationService", "SaasService", "ProductService"}).ToList(),
redirectUris: new List<string> { $"{webClientRootUrl}signin-oidc" },
postLogoutRedirectUris: new List<string>() { $"{webClientRootUrl}signout-callback-oidc" }
);
//Blazor Client
var blazorClientRootUrl = _configuration["OpenIddict:Applications:BookStore_Blazor:RootUrl"].EnsureEndsWith('/');
await CreateApplicationAsync(
name: "BookStore_Blazor",
type: OpenIddictConstants.ClientTypes.Public,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Blazor Client",
secret: null,
grantTypes: new List<string>
{
OpenIddictConstants.GrantTypes.AuthorizationCode
},
scopes: commonScopes.Union(new[] {"AccountService", "IdentityService", "AdministrationService", "SaasService", "ProductService"}).ToList(),
redirectUris: new List<string> { $"{blazorClientRootUrl}authentication/login-callback" },
postLogoutRedirectUris: new List<string> { $"{blazorClientRootUrl}authentication/logout-callback" }
);
//Blazor Server Client
var blazorServerClientRootUrl = _configuration["OpenIddict:Applications:BookStore_BlazorServer:RootUrl"].EnsureEndsWith('/');
await CreateApplicationAsync(
name: "BookStore_BlazorServer",
type: OpenIddictConstants.ClientTypes.Confidential,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Blazor Server Client",
secret: "1q2w3e*",
grantTypes: new List<string> //Hybrid flow
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.Implicit
},
scopes: commonScopes.Union(new[] {"AccountService", "IdentityService", "AdministrationService", "SaasService", "ProductService" }).ToList(),
redirectUris: new List<string> { $"{blazorServerClientRootUrl}signin-oidc" },
postLogoutRedirectUris: new List<string> { $"{blazorServerClientRootUrl}signout-callback-oidc" }
);
//Public Web Client
var publicWebClientRootUrl = _configuration["OpenIddict:Applications:BookStore_PublicWeb:RootUrl"].EnsureEndsWith('/');
await CreateApplicationAsync(
name: "BookStore_PublicWeb",
type: OpenIddictConstants.ClientTypes.Confidential,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Public Web Client",
secret: "1q2w3e*",
grantTypes: new List<string> //Hybrid flow
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.Implicit
},
scopes: commonScopes.Union(new[] { "AccountService", "AdministrationService", "ProductService" }).ToList(),
redirectUris: new List<string> { $"{publicWebClientRootUrl}signin-oidc" },
postLogoutRedirectUris: new List<string> { $"{publicWebClientRootUrl}signout-callback-oidc" }
);
//Angular Client
var angularClientRootUrl = _configuration["OpenIddict:Applications:BookStore_Angular:RootUrl"].TrimEnd('/');
await CreateApplicationAsync(
name: "BookStore_Angular",
type: OpenIddictConstants.ClientTypes.Public,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Angular Client",
secret: null,
grantTypes: new List<string>
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.RefreshToken,
OpenIddictConstants.GrantTypes.Password,
"LinkLogin",
"Impersonation"
},
scopes: commonScopes.Union(new[] {"AccountService", "IdentityService", "AdministrationService", "SaasService", "ProductService" }).ToList(),
redirectUris: new List<string> { $"{angularClientRootUrl}" },
postLogoutRedirectUris: new List<string> { $"{angularClientRootUrl}" }
);
//Administration Service Client
await CreateApplicationAsync(
name: "BookStore_AdministrationService",
type: OpenIddictConstants.ClientTypes.Confidential,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Administration Service Client",
secret: "1q2w3e*",
grantTypes: new List<string>
{
OpenIddictConstants.GrantTypes.ClientCredentials
},
scopes: commonScopes.Union(new[] { "IdentityService" }).ToList(),
permissions: new List<string> { IdentityPermissions.Users.Default }
);
}
private async Task CreateApplicationAsync(
[NotNull] string name,
[NotNull] string type,
[NotNull] string consentType,
string displayName,
string secret,
List<string> grantTypes,
List<string> scopes,
List<string> redirectUris = null,
List<string> postLogoutRedirectUris = null,
List<string> permissions = null)
{
if (!string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
throw new BusinessException(L["NoClientSecretCanBeSetForPublicApplications"]);
}
if (string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
{
throw new BusinessException(L["TheClientSecretIsRequiredForConfidentialApplications"]);
}
if (!string.IsNullOrEmpty(name) && await _applicationManager.FindByClientIdAsync(name) != null)
{
return;
//throw new BusinessException(L["TheClientIdentifierIsAlreadyTakenByAnotherApplication"]);
}
var client = await _applicationManager.FindByClientIdAsync(name);
if (client == null)
{
var application = new OpenIddictApplicationDescriptor
{
ClientId = name,
Type = type,
ClientSecret = secret,
ConsentType = consentType,
DisplayName = displayName
};
Check.NotNullOrEmpty(grantTypes, nameof(grantTypes));
Check.NotNullOrEmpty(scopes, nameof(scopes));
if (new [] { OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.Implicit }.All(grantTypes.Contains))
{
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken);
if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken);
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeToken);
}
}
if (!redirectUris.IsNullOrEmpty() || !postLogoutRedirectUris.IsNullOrEmpty())
{
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Logout);
}
var buildInGrantTypes = new []
{
OpenIddictConstants.GrantTypes.Implicit,
OpenIddictConstants.GrantTypes.Password,
OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.ClientCredentials,
OpenIddictConstants.GrantTypes.DeviceCode,
OpenIddictConstants.GrantTypes.RefreshToken
};
foreach (var grantType in grantTypes)
{
if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode)
{
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode);
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code);
}
if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode || grantType == OpenIddictConstants.GrantTypes.Implicit)
{
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization);
}
if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode ||
grantType == OpenIddictConstants.GrantTypes.ClientCredentials ||
grantType == OpenIddictConstants.GrantTypes.Password ||
grantType == OpenIddictConstants.GrantTypes.RefreshToken ||
grantType == OpenIddictConstants.GrantTypes.DeviceCode)
{
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Revocation);
application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection);
}
if (grantType == OpenIddictConstants.GrantTypes.ClientCredentials)
{
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.ClientCredentials);
}
if (grantType == OpenIddictConstants.GrantTypes.Implicit)
{
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Implicit);
}
if (grantType == OpenIddictConstants.GrantTypes.Password)
{
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Password);
}
if (grantType == OpenIddictConstants.GrantTypes.RefreshToken)
{
application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken);
}
if (grantType == OpenIddictConstants.GrantTypes.Implicit)
{
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdToken);
if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
{
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken);
application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Token);
}
}
if (!buildInGrantTypes.Contains(grantType))
{
application.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.GrantType + grantType);
}
}
var buildInScopes = new []
{
OpenIddictConstants.Permissions.Scopes.Address,
OpenIddictConstants.Permissions.Scopes.Email,
OpenIddictConstants.Permissions.Scopes.Phone,
OpenIddictConstants.Permissions.Scopes.Profile,
OpenIddictConstants.Permissions.Scopes.Roles
};
foreach (var scope in scopes)
{
if (buildInScopes.Contains(scope))
{
application.Permissions.Add(scope);
}
else
{
application.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope);
}
}
if (!redirectUris.IsNullOrEmpty())
{
foreach (var redirectUri in redirectUris)
{
if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString())
{
throw new BusinessException(L["InvalidRedirectUri", redirectUri]);
}
if (application.RedirectUris.All(x => x != uri))
{
application.RedirectUris.Add(uri);
}
}
}
if (!postLogoutRedirectUris.IsNullOrEmpty())
{
foreach (var postLogoutRedirectUri in postLogoutRedirectUris)
{
if (!Uri.TryCreate(postLogoutRedirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString())
{
throw new BusinessException(L["InvalidPostLogoutRedirectUri", postLogoutRedirectUri]);
}
if (application.PostLogoutRedirectUris.All(x => x != uri))
{
application.PostLogoutRedirectUris.Add(uri);
}
}
}
if (permissions != null)
{
await _permissionDataSeeder.SeedAsync(
ClientPermissionValueProvider.ProviderName,
name,
permissions,
null
);
}
await _applicationManager.CreateAsync(application);
}
}
}
@tcables
Copy link

tcables commented May 9, 2023

Hi @gterdem, there is one issue with this code. CreateApplicationAsync is missing the Introspection Endpoint Permission. This causes some issues with login timeouts in the UI. If you could please update the CreateApplicationAsync method to match the ABP OpenId Upgrade sample code at https://github.com/abpframework/abp-samples/blob/733bb038d7161be382e9824f9dc2f363bdd86aff/Ids2OpenId/src/Ids2OpenId.Domain/OpenIddict/OpenIddictDataSeedContributor.cs#LL261C38-L261C38 it could help others in the future.

if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode ||
    grantType == OpenIddictConstants.GrantTypes.ClientCredentials ||
    grantType == OpenIddictConstants.GrantTypes.Password ||
    grantType == OpenIddictConstants.GrantTypes.RefreshToken ||
    grantType == OpenIddictConstants.GrantTypes.DeviceCode)
{
    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Revocation);
    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection); //This line
}

@gterdem
Copy link
Author

gterdem commented May 10, 2023

Hi @gterdem, there is one issue with this code. CreateApplicationAsync is missing the Introspection Endpoint Permission. This causes some issues with login timeouts in the UI. If you could please update the CreateApplicationAsync method to match the ABP OpenId Upgrade sample code at https://github.com/abpframework/abp-samples/blob/733bb038d7161be382e9824f9dc2f363bdd86aff/Ids2OpenId/src/Ids2OpenId.Domain/OpenIddict/OpenIddictDataSeedContributor.cs#LL261C38-L261C38 it could help others in the future.

if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode ||
    grantType == OpenIddictConstants.GrantTypes.ClientCredentials ||
    grantType == OpenIddictConstants.GrantTypes.Password ||
    grantType == OpenIddictConstants.GrantTypes.RefreshToken ||
    grantType == OpenIddictConstants.GrantTypes.DeviceCode)
{
    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Revocation);
    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection); //This line
}

Thank you, I have updated it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment