twobotechnologies

Federation · 4 min read

AD FS Claim Rules for LDAP Attribute Stores, Line by Line

Short answer

An AD FS rule that reads an LDAP attribute store has a condition that selects an input claim, then an issue or add statement with store (the attribute store name), types (output claim types in order), query (an LDAP filter, a semicolon, and a comma-separated attribute list) and param (values that replace {0}, {1} in the filter).

Key takeaways

  • Query format: LDAP filter; comma-separated attributes; and, for Active Directory stores only, DOMAIN\user.
  • The order of types must match the order of attributes in the query.
  • Use add instead of issue to keep an intermediate claim for later rules without sending it to the relying party.

The AD FS claim rule language is compact and unforgiving. A missing semicolon or an unescaped backslash produces an error at best and silently empty claims at worst. This is a reference for rules that query an LDAP attribute store. If you have not created the store yet, start with the LDAP attribute store setup article.

What does a complete rule look like?

A condition, the => operator, and an issuance statement that names the store, output types, query and parameters.

@RuleName = "Customer number from Partner LDS"
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
 => issue(store = "Partner LDS",
          types = ("http://example.com/claims/customernumber",
                   "http://schemas.xmlsoap.org/claims/Group"),
          query = "(&(objectClass=inetOrgPerson)(mail={0}));customerNumber,memberOf",
          param = c.Value);

What does each part mean?

Take it line by line.

Part Meaning
@RuleName = "..." Optional label shown in the console and logs
c:[Type == "...upn"] Condition: match an input claim of this type and call it c
=> "Then"
issue(...) Add the resulting claims to the output (sent to the relying party)
store = "Partner LDS" Attribute store name, exactly as configured
types = (...) Output claim types, one per returned attribute, in order
query = "...;...;..." LDAP filter, then attribute list, then (AD only) domain\user
param = c.Value Value substituted for {0} in the query

How is the query string structured?

<filter>;<attributes>;<DOMAIN\user>, where the third part is only used for Active Directory stores and ignored for LDAP stores.

  • Filter. Any valid LDAP search filter (RFC 4515), such as (&(objectClass=user)(uid={0})). Placeholders {0}, {1} and so on take values from param in order.
  • Attributes. A comma-separated list, no spaces: mail,customerNumber,memberOf.
  • Domain\user. For Active Directory stores, this tells AD FS which domain controller to use. For LDAP stores, leave it out or it is ignored.

The store returns a table: one row per matching object, one column per attribute. Each value becomes a claim of the type in the matching position of types. Multi-valued attributes produce one claim per value.

How do multiple parameters work?

Repeat param, once per placeholder, in order.

c1:[Type == "http://example.com/claims/tenant"]
 && c2:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
 => issue(store = "Partner LDS",
          types = ("http://example.com/claims/plan"),
          query = "(&(o={0})(mail={1}));plan",
          param = c1.Value, param = c2.Value);

What escaping rules apply?

Claim rule strings use backslash as an escape character, and LDAP filters have their own escaping for special characters.

  • In the rule language, write a literal backslash as \\. A regex that strips DOMAIN\ looks like regexreplace(c.Value, "^.*\\", "").
  • In LDAP filters, the characters *, (, ), \ and NUL must be escaped in values as \2a, \28, \29, \5c and \00. Be careful when a parameter comes from user-controlled input; a crafted value could change the filter.

When should I use add instead of issue?

When the looked-up value is only needed by a later rule.

c:[Type == "...upn"]
 => add(store = "Partner LDS", types = ("http://example.com/internal/region"),
        query = "(mail={0});region", param = c.Value);

c:[Type == "http://example.com/internal/region", Value == "EU"]
 => issue(Type = "http://schemas.xmlsoap.org/claims/Group", Value = "eu-users");

The first rule stores the region as an input claim only. The second turns it into a group claim for EU users. The relying party never sees the internal region claim.

How do I debug a rule?

Check the AD FS admin event log, test the LDAP filter with an LDAP tool using the service account, and decode the resulting token.

Run the same filter with ldp.exe or ldapsearch against the same base DN. Then sign in to a test relying party and paste the SAMLResponse into the SAML Decoder to see exactly which claims arrived. The federation guide covers attribute release more broadly.

Questions

What is the difference between issue and add in AD FS claim rules?

issue puts the claim in the output and in the input set for later rules, so it is sent to the relying party. add only places it in the input set, which is useful for intermediate values used by later rules.

Why does my query return nothing?

Common causes are a domain prefix left in the parameter, the wrong object class in the filter, an attribute name that differs in the target directory, or the service account lacking read access. The AD FS admin event log usually records the error.

Sources

Tagged: AD FS, LDAP, Microsoft, Federation

The Twobo Technologies editors write practical explainers on identity and API security and maintain the free tools on this site. Spotted an error or an outdated detail? Email contact@twobotechnologies.com.

Related articles