Customizing IdentityServer

IdentityServer was designed with extensibility in mind. And since the question how to do that comes up quite frequently, here’s a overview to get you started.

Certain parts of IdSrv that we thought might need to be extended or customized are abstracted using interfaces – e.g. how to store and retrieve certificates or how to authenticate a user. You can find all the interface in the Core project’s Repositories folder.

Configuration data storage & retrieval

  • IConfigurationRepository (general and protocols configuration)
  • IClientCertificateRepository (X.509 client certificates and their mappings to user accounts)
  • IClientRepository (OAuth2 clients)
  • IDelegationRepository (WS-Trust identity delegation mappings)
  • IIdentityProviderRepository (WS* and OAuth2 identity providers)
  • IRelyingPartyRepository (relying parties and resources)

Runtime data access

  • ICodeTokenRepository (OAuth2 authorization codes and refresh token handling)
  • IClaimsRepository (claims retrieval for token generation)
  • IClaimsTransformationRulesRepository (claims transformation logic for external identity providers)
  • IUserManagementRepository (user management)
  • IUserRepository (user authentication)

All these extensibility points are wired up using MEF. MEF in turn reads from the repositories.config file in the WebSite project. So in other words, when you want to customize a certain part of IdSrv, implement the right interface and replace the default implementation with yours in repositories.config.

Most common scenario: customize authentication and claims retrieval
By far the most common task is to adapt IdSrv to an existing credential and attribute store. That’s actually quite easy to achieve.

The IUserRepository interface takes care of authentication:

public interface IUserRepository

{

    bool ValidateUser(string userName, string password);

    bool ValidateUser(X509Certificate2 clientCertificate, out string userName);

    IEnumerable<string> GetRoles(string userName);       

}

 

ValidateUser does either username/password or client certificate based authentication. You only need to implement what you want to support.
GetRoles return roles for IdSrv internal authorization, e.g. users (that can request tokens) have to be in the IdentityServerUsers role. Admins (that can administer IdSrv but can’t request tokens) have to be in the IdentityServerAdministrators role.

The IClaimsRepository interface abstracts claims generation and the corresponding metadata:

public interface IClaimsRepository
{
    IEnumerable<Claim> GetClaims(ClaimsPrincipal principal, 
RequestDetails
requestDetails);
    IEnumerable<string> GetSupportedClaimTypes();
}

 

GetClaims returns a list of claims for a given principal. The requestDetails parameter has information about the relying party/resource the token is for.
GetSupportClaimTypes is used to augment the WS-Federation metadata document with the supported claim types.

HTH

PS. Yes I am aware that these extensibility points are not really repositories. Historically they were named like this, and that’s now the way it is ;)

This entry was posted in IdentityServer. Bookmark the permalink.

16 Responses to Customizing IdentityServer

  1. William Scalf says:

    This post has interesting timing!

    We discussed possible contribution/extension ideas awhile back on Codeplex (https://identityserver.codeplex.com/discussions/395883), and I’ve had some free time recently to start working on a generic implementation of IClaimsTransformationRuleRepository. It’s somewhat inspired by the Claims Transformation Rule Language from ADFS, though somewhat simplified, the goal being to allow users to be able to cover common transformation scenarios with configuration instead of necessarily having to write their own plugins.

    If you still have some interest, perhaps we should talk more about it. :)

    • Hi,

      cool! So if you have time and ideas – go for it!

      I will not have any free cycles in the next months, but you can keep me certainly posted. Please use the issue tracker on github so we can have a discussion.

  2. Yep – just to see if anyone has feature ideas.

  3. Corey Cummings says:

    Dominick, I have implemented my own IClaimsRepository concreate type, and wired it up in repositories.config. The getclaims() method is never called. I am following the example of using the web AccountController SignIn routine, it doesn’t seem like it ever calls an IClaimsRepository instance in any of the code paths, should I be using a different routine? Thanks for any help.

    • It gets called whenever a token gets created. Do you use any of the token endpoints?

      • Corey Cummings says:

        I was testing using the issue/wsfed endpoint. I was testing simple forms authentication and looking to add custom claims to the principal in addition to the ones being defaulted in the authenticationhelper class. I assume I would have to do the work before the SignIn action of the AccountControllerBase? and pass in the custom claims I want added?

  4. Well – either woud work. But the claims repository *must* be called in the pipeline – maybe something is wrong with your debugging.

  5. Ionut Calin says:

    Hello Dominick,
    I have the same problem with GetClaims method of IClaimsRepository which is not called in the pipeline of FormsAuthentication scenario (meaning SignIn method of AccountController)
    I’ve also opened an issue on git repository for this (#291).

  6. Pingback: Using IdentityServer to issue tokens for Windows Server ServiceBus | www.leastprivilege.com

  7. Ernesto Medina says:

    Hello Dominic,

    I’m using the IdentityServer for a while, and I have this scenario:
    – The STS
    – My Website (with passive redirection to the STS)
    – A WCF service (Claims aware of course)

    When a user log in into to my website, the STS load those claims that are useful for my site and send the token back. So when I want to call the wcf backend from my website I send a request for an ActAs token and use this new token to invoke it. What I want achieve (I don’t know if is possible) is to get the request for an ActAs token sent to the STS from my website and due that this new token will be issued for a different realm (my wcf service in this case) I want to load new claims for the real user that I’m delegating the identity for. At the end the claims I got in first authentication were only intended to be used by my website and are not the same required for the backend.

    I want to know if this is possible and in a positive answer what kind of customization I need to do to the IdentityServer code.

  8. Melissa R. says:

    Hi, I have a question. I have two applications that interface with one another and share login credentials (but separate permissions and separate databases). I have been using identity server for “app 1” (aka DM) for authentication against a preexisting user database (from “app 2″ aka PC) and have implemented my own user repository class (PCUserRepository) from IUserRepository. This works just fine.

    In the website repositories.config file I have the following setting for user validation:
    userValidation=”DM.IdSrv.Repositories.PCUserRepository, DM.IdSrv.Repositories”

    Now, I have a third application which will essentially replace “app 2” (PC) based on which one the user has purchased. This means “app 1” (DM) will need to authenticate against a completely different user store, “app 3” (S1). For this, I have created a second user repository class S1UserRepository from IUserRepository.

    My question comes into play here – how can I change the userValidation config setting at runtime to use the S1UserRepository based on which application (PC or S1) the user has purchased? I have a field in my “app 1” (DM) database that indicates which application has been purchased. I’m certain that other applications will be added in the future as well.

    Thanks in advance for any assistance you can provide.

  9. Ken Todd says:

    Hello. Are there any examples to go off of that show how to adapt identity server to an LDAP? Understand that this is fairly common scenario, but haven’t been able to find good examples of how to do so. Thanks.

Leave a comment