Werther 1
Werther is an Identity Provider for ORY Hydra over LDAP. It implements Login And Consent Flow and provides basic UI.
Features
- Support Active Directory;
- Mapping LDAP attributes to OpenID Connect claims;
- Mapping LDAP groups to user roles;
- OAuth 2.0 scopes;
- Caching users roles;
- UI customization.
Limitations
- Werther grants all requested permissions to a client without displaying the consent page;
- Werther confirms a logout request without displaying the logout confirmation page.
Requirements
ORY Hydra v1.0.0-rc.12 or higher.
Table of Contents
- Installing
- Configuration
- User roles
- UI customization
- Example
- Resources
- Footnotes
- Contributing
- License
docker pull icoreru/werther
go install ./...
The application is configured via environment variables.
Names of the environment variables starts with prefix WERTHER_
.
See a list of the environment variables using the command:
werther -h
In LDAP user's roles are groups in which a user is a member.
The environment variable WERTHER_LDAP_ROLE_BASEDN
is a DN for searching roles.
For example, create an OU that repserents an application, and then in the created OU create groups that represent application's roles:
dc=com
|-- dc=example
|-- ou=AppRoles
|-- ou=App1
|-- cn=app1_role1 (objectClass="group", description="role1")
|-- cn=app1_role2 (objectClass="group", description="role2")
Run Werther with the environment variable WERTHER_LDAP_ROLE_BASEDN
that equals to ou=AppRoles,dc=example,dc=com
.
In the above example Werther returns user's roles as a value
of the user role's claim https://github.com/i-core/werther/claims/roles
.
{
"https://github.com/i-core/werther/claims/roles": {
"App1": ["role1", "role2"],
}
}
To customize the roles claim's name you should set a value of the environment variable WERTHER_LDAP_ROLE_CLAIM
.
Also you should map the custom name of the roles' claim to a roles's scope using the environment variable
WERTHER_IDENTP_CLAIM_SCOPES
(the name must be URL encoded):
env WERTHER_LDAP_ROLE_CLAIM=https://my-company.com/claims/roles \
WERTHER_IDENTP_CLAIM_SCOPES=name:profile,family_name:profile,given_name:profile,email:email,https%3A%2F%2Fmy-company.com%2Fclaims%2Froles:roles \
werther
For more details about claims naming see OpenID Connect Core 1.0.
NB There are cases when we need to create several roles with the same name in LDAP. For example, when we want to configure multiple applications or several environments for the same application.
dc=com
|-- dc=example
|-- ou=AppRoles
|-- ou=Test
|-- ou=App1
|-- cn=test_app1_role1 (objectClass="group", description="role1")
|-- cn=test_app1_role2 (objectClass="group", description="role2")
|-- ou=App2
|-- cn=test_app2_role1 (objectClass="group",description-"role1")
|-- cn=test_app2_role2 (objectClass="group",description-"role2")
|-- ou=Dev
|-- ou=App1
|-- cn=dev_app1_role1 (objectClass="group", description="role1")
|-- cn=dev_app1_role3 (objectClass="group", description="role3")
|-- ou=App2
|-- cn=dev_app2_role1 (objectClass="group",description-"role1")
|-- cn=dev_app2_role4 (objectClass="group",description-"role4")
Active Directory requires unique CNs in a domain. But in Active Directory
creating groups with the same CN in different OUs is difficult.
Because of it, Werther uses a LDAP attribute as a role's name instead of CN.
A name of a LDAP attribute is specified using the environment variable WERTHER_LDAP_ROLE_ATTR
,
and has the default value description
.
In the above example, Werther returns a response that contains the next roles:
- when the environment variable
WERTHER_LDAP_ROLE_BASEDN
equals toou=Test,ou=AppRoles,dc=example,dc=com
:{ "https://github.com/i-core/werther/claims/roles": { "App1": ["role1", "role2"], "App2": ["role1", "role2"] } }
- when the environment variable
WERTHER_LDAP_ROLE_BASEDN
equals toou=Dev,ou=AppRoles,dc=example,dc=com
:{ "https://github.com/i-core/werther/claims/roles": { "App1": ["role1", "role3"], "App2": ["role1", "role4"] } }
If your applications expect the roles claim to be an array of strings, for example Concourse or Argo CD,
you can add groups to claims using with the environment variable WERTHER_LDAP_FLAT_ROLE_CLAIMS
.
When it is true Werther add corresponding claims for all the apps as an array of roles.
Example 1:
WERTHER_LDAP_FLAT_ROLE_CLAIMS=false
{
"https://github.com/i-core/werther/claims/roles": {
"App1": ["role1", "role2"],
"App2": ["role3", "role4"]
}
}
Example 2:
WERTHER_LDAP_FLAT_ROLE_CLAIMS=true
{
"https://github.com/i-core/werther/claims/roles": {
"App1": ["role1", "role2"],
"App2": ["role3", "role4"]
},
"https://github.com/i-core/werther/claims/roles/App1": ["role1", "role2"],
"https://github.com/i-core/werther/claims/roles/App2": ["role3", "role4"]
}
Werther uses the Go templates to render UI pages.
To customize the UI you should create a directory that contains UI pages' templates.
After that you should set the directory path to the environment variable WERTHER_WEB_DIR
.
A login page's template must be a Go template. The template has access to data conforming the next JSON-schema:
type: object
properties:
- WebBasePath:
description: The base path of the login page
type: string
- LangPrefs:
description: The user language preferences (the parsed value of the header Accept-Language)
type: array
items:
type: object
properties:
- Lang:
description: The language canonical name.
type: string
- Weight:
description: The language weight.
type: number
required:
- Lang
- Weight
- Data:
type: object
properties:
- CSRFToken:
description: A CSRF token.
type: string
- Challenge:
description: A login challenge ID.
type: string
- LoginURL:
description: An endpoint that finishes the login process.
type: string
- IsInvalidCredentials:
description: Specifies that a user types an invalid username or password.
type: boolean
- IsInternalError:
description: Specifies that an internal server error happens when finishing the login process.
type: boolean
required:
- CSRFToken
- Challenge
- LoginURL
- IsInvalidCredentials
- IsInternalError
required:
- WebBasePath
- LangPrefs
- Data
When a login page's template contains static resources (like styles, scripts, and images)
they must be placed in a subdirectory called static
.
For a full example of a login page's template see source code.
The old template format is also supported but it will be removed in the future major release.
A login page's template should contains blocks title
, style
, script
, content
.
Each block has access to data conforming the next JSON-schema:
type: object
properties:
- CSRFToken:
description: A CSRF token.
type: string
- Challenge:
description: A login challenge ID.
type: string
- LoginURL:
description: An endpoint that finishes the login process.
type: string
- IsInvalidCredentials:
description: Specifies that a user types an invalid username or password.
type: boolean
- IsInternalError:
description: Specifies that an internal server error happens when finishing the login process.
type: boolean
required:
- CSRFToken
- Challenge
- LoginURL
- IsInvalidCredentials
- IsInternalError
When a login page's template contains static resources (like styles, scripts, and images)
they must be placed in a subdirectory called static
.
For a full example of a login page's template see source code.
-
Create file
ldap.ldif
:dn: uid=kolya_gerasyimov,ou=Users,dc=example,dc=com objectClass: inetOrgPerson cn: Kolya Gerasyimov sn: Gerasyimov uid: kolya_gerasyimov userPassword: 123 mail: [email protected] ou: Users dn: ou=AppRoles,dc=example,dc=com objectClass: organizationalunit ou: AppRoles description: AppRoles dn: ou=App1,ou=AppRoles,dc=example,dc=com objectClass: organizationalunit ou: App1 description: App1 dn: cn=traveler,ou=App1,ou=AppRoles,dc=example,dc=com objectClass: groupofnames cn: traveler description: traveler member: uid=kolya_gerasyimov,ou=Users,dc=example,dc=com
-
Create file
docker-compose.yml
:version: "3" services: hydra-client: image: oryd/hydra:v1.0.0-rc.12 environment: HYDRA_ADMIN_URL: http://hydra:4445 command: - clients - create - --skip-tls-verify - --id - test-client - --secret - test-secret - --response-types - id_token,token,"id_token token" - --grant-types - implicit - --scope - openid,profile,email,roles - --callbacks - http://localhost:3000 - --post-logout-callbacks - http://localhost:3000/post-logout-callback networks: - hydra-net deploy: restart_policy: condition: none depends_on: - hydra healthcheck: test: ["CMD", "curl", "-f", "http://hydra:4445"] interval: 10s timeout: 10s retries: 10 hydra: image: oryd/hydra:v1.0.0-rc.12 environment: URLS_SELF_ISSUER: http://localhost:4444 URLS_SELF_PUBLIC: http://localhost:4444 URLS_LOGIN: http://localhost:8080/auth/login URLS_CONSENT: http://localhost:8080/auth/consent URLS_LOGOUT: http://localhost:8080/auth/logout WEBFINGER_OIDC_DISCOVERY_SUPPORTED_SCOPES: profile,email,phone,roles WEBFINGER_OIDC_DISCOVERY_SUPPORTED_CLAIMS: name,family_name,given_name,nickname,email,phone_number,https://github.com/i-core/werther/claims/roles DSN: memory command: serve all --dangerous-force-http networks: - hydra-net ports: - "4444:4444" - "4445:4445" deploy: restart_policy: condition: on-failure depends_on: - werther werther: image: icoreru/werther:v1.1.1 environment: WERTHER_IDENTP_HYDRA_URL: http://hydra:4445 WERTHER_LDAP_ENDPOINTS: ldap:389 WERTHER_LDAP_BINDDN: cn=admin,dc=example,dc=com WERTHER_LDAP_BINDPW: password WERTHER_LDAP_BASEDN: "dc=example,dc=com" WERTHER_LDAP_ROLE_BASEDN: "ou=AppRoles,dc=example,dc=com" networks: - hydra-net ports: - "8080:8080" deploy: restart_policy: condition: on-failure depends_on: - ldap ldap: image: pgarrett/ldap-alpine volumes: - "./ldap.ldif:/ldif/ldap.ldif" networks: - hydra-net ports: - "389:389" deploy: restart_policy: condition: on-failure networks: hydra-net:
-
Run the command:
docker stack deploy -c docker-compose.yml auth
-
Open the browser with http://localhost:4444/oauth2/auth?client_id=test-client&response_type=token&scope=openid%20profile%20email%20roles&state=12345678.
- Introduction to ORY Hydra, OAuth 2.0, and OpenID Connect;
- ORY Hydra: Integrating with (existing) User Management;
- ORY Hydra: Configuration;
- ORY Hydra: Official User Login & Consent Example;
- OpenID Connect Core 1.0;
- OpenID Connect Session Management 1.0;
- OpenID Connect Front-Channel Logout 1.0;
- OpenID Connect Back-Channel Logout 1.0.
- Werther is named after robot Werther from Guest from the Future.
Thanks for your interest in contributing to this project. Get started with our Contributing Guide.
The code in this project is licensed under MIT license.