Information Security 26 min read

Practical Implementation of IdentityServer4 in the Fuluteng Project

This article provides a hands‑on guide to using IdentityServer4 in the Fuluteng project, covering signing credentials, client and resource stores, persisted grant storage with Redis, various OAuth2 grant types including client credentials, authorization code, password, and custom extensions such as SMS and third‑party logins, and demonstrates role‑based authorization and JWT configuration.

Fulu Network R&D Team
Fulu Network R&D Team
Fulu Network R&D Team
Practical Implementation of IdentityServer4 in the Fuluteng Project

The article introduces a practical tutorial on integrating IdentityServer4 within the Fuluteng project, assuming readers are already familiar with OAuth2 fundamentals.

Signing Credentials – IdentityServer supports X.509 certificates, RSA, and EC keys for token signing. For development, AddDeveloperSigningCredential() is used, while production requires loading a certificate via AddSigningCredential() . Sample code for generating an X.509 certificate with BouncyCastle is provided.

var issuer = new X509Name(new ArrayList{X509Name.C,X509Name.O,X509Name.OU,X509Name.L,X509Name.ST},
    new Hashtable{[X509Name.C] = "CN",[X509Name.O] = "Fulu Newwork",[X509Name.OU] = "Fulu RSA CA 2020",[X509Name.L] = "Wuhan",[X509Name.ST] = "Hubei"});
var subject = new X509Name(new ArrayList{X509Name.C,X509Name.O,X509Name.CN},
    new Hashtable {[X509Name.C] = "CN",[X509Name.O] = "ICH",[X509Name.CN] = "*.fulu.com"});
CertificateGenerator.GenerateCertificate(new CertificateGeneratorOptions { Path = "mypfx.pfx", Issuer = issuer, Subject = subject });

After generating mypfx.pfx , it can be loaded with new X509Certificate2("mypfx.pfx", "password", X509KeyStorageFlags.Exportable) and added to IdentityServer via identityServerBuilder.AddSigningCredential(certificate2) . The project stores the certificate as a hex string in configuration for easier deployment.

Client Store – A custom ClientStore class implements IClientStore . It retrieves client data from a cache, sets common grant types (authorization code, client credentials, resource owner password, and custom grants), and adds role claims ( JwtClaimTypes.Role = "Client" ) for client tokens.

public class ClientStore : IClientStore {
    public async Task
FindClientByIdAsync(string clientId) {
        var clientEntity = await _clientInCacheRepository.GetClientByIdAsync(clientId.ToInt32());
        if (clientEntity == null) return null;
        return new Client {
            ClientId = clientId,
            AllowedScopes = new[] { "api", "get_user_info" },
            ClientSecrets = new[] { new Secret(clientEntity.ClientSecret.Sha256()) },
            AllowedGrantTypes = new[] { GrantType.AuthorizationCode, GrantType.ClientCredentials, GrantType.ResourceOwnerPassword, CustomGrantType.External, CustomGrantType.Sms },
            Claims = new[] { new Claim(JwtClaimTypes.Role, ClaimRoles.Client) }
        };
    }
}

Persisted Grant Store – The project uses Redis to persist grants. An IPersistedGrantStore implementation stores each grant as a Redis hash, maintains expiration, and keeps auxiliary lists for fast lookup by subject ID, client ID, and grant type. Sample methods for StoreAsync , GetAsync , GetAllAsync , and removal operations are shown.

public async Task StoreAsync(PersistedGrant grant) {
    var db = await _redisCache.GetDatabaseAsync();
    var trans = db.CreateTransaction();
    var expiry = grant.Expiration.Value.ToLocalTime();
    db.HashSetAsync(grant.Key, GetHashEntries(grant));
    db.KeyExpireAsync(grant.Key, expiry);
    db.ListLeftPushAsync(grant.SubjectId, grant.Key);
    db.KeyExpireAsync(grant.SubjectId, expiry);
    await trans.ExecuteAsync();
}

Resource Owner Password Validator – Implements IResourceOwnerPasswordValidator to validate username/password against the user service and issue a GrantValidationResult with appropriate claims.

public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) {
    var result = await _userService.LoginByPasswordAsync(context.UserName, context.Password);
    if (result.Code == 0) {
        var claims = await _userService.SaveSuccessLoginInfo(...);
        context.Result = new GrantValidationResult(result.Data.Id, OidcConstants.AuthenticationMethods.Password, claims);
    } else {
        context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, result.Message);
    }
}

Redirect URI Validator – Validates that the requested redirect URI matches the host of any registered URIs for the client.

public Task
IsRedirectUriValidAsync(string requestedUri, Client client) {
    if (client.RedirectUris == null || !client.RedirectUris.Any())
        return Task.FromResult(false);
    var uri = new Uri(requestedUri);
    return Task.FromResult(client.RedirectUris.Any(x => x.Contains(uri.Host)));
}

Custom Extension Grants – Two custom grant types are demonstrated: SMS‑based login and third‑party (QQ/WeChat) login. The SmsGrantValidator validates phone numbers and SMS codes, retrieves or creates a user, records login info, and returns a token.

public class SmsGrantValidator : IExtensionGrantValidator {
    public string GrantType => CustomGrantType.Sms;
    public async Task ValidateAsync(ExtensionGrantValidationContext context) {
        var phone = context.Request.Raw.Get("phone");
        var code = context.Request.Raw.Get("code");
        // validate phone and code, then issue token
    }
}

The article also explains role‑based authorization using JWT claims ( role = "Client" for client credentials and role = "User" for user tokens) and shows how to configure ASP.NET Core authentication to map the role claim correctly.

OAuth2 Grant Flows – Detailed step‑by‑step examples for client‑credentials, authorization‑code, password, and custom extension grants are provided, including HTTP request samples, expected responses, and token payloads.

Third‑Party Login (WeChat example) – Shows how to create a custom OAuth handler for WeChat, define endpoints, map JSON fields to claims, build the challenge URL, exchange the code for an access token, retrieve user info, and integrate the handler into the ASP.NET Core authentication pipeline.

public class WeChatOptions : OAuthOptions {
    public WeChatOptions() {
        CallbackPath = new PathString("/signin-wechat");
        AuthorizationEndpoint = "https://open.weixin.qq.com/connect/qrconnect";
        TokenEndpoint = "https://api.weixin.qq.com/sns/oauth2/access_token";
        UserInformationEndpoint = "https://api.weixin.qq.com/sns/userinfo";
        Scope.Add("snsapi_login");
        ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "openid");
        ClaimActions.MapJsonKey(ClaimTypes.Name, "nickname");
    }
}

Finally, the article demonstrates how to register the WeChat handler in Startup.ConfigureServices and how to initiate and handle the external login flow in a controller.

RedisauthenticationOAuth2ASP.NET CoreCustom GrantIdentityServer4
Fulu Network R&D Team
Written by

Fulu Network R&D Team

Providing technical literature sharing for Fulu Holdings' tech elite, promoting its technologies through experience summaries, technology consolidation, and innovation sharing.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.