GitHub user serra edited a discussion: Use Authelia as OpenID Connect
authorization provider in Superset
## Context
[Authelia] is a 2FA & SSO authentication server which is dedicated to the
security of applications and users.
We use Authelia for SSO across several web applications, Superset amongst
others.
In our case, Authelia is backed by [LLDAP] as a directory service.
## Requirements
- Given a user is in the directory, when they login successfully, they should
be authorized with the `Gamma` role.
- Given a user is in the `superset_admin` group in LLDAP, then they should get
the `Admin` role in superset.
- Given a user is in additional LLDAP groups and those groups are roles in
Superset, then they should get these roles in Superset.
## Solution
Superset is built using [Flask App Builder] (FAB), which comes with
authorization support for LDAP and OAuth2 amongst others.
We can configure an OpenID Connect (OIDC) client in Authelia, and then consume
this service as an OAuth2 client from Superset.
OIDC is an extension of OAuth2. OIDC is not explicitly supported by FAB. We can
however use the OIDC endpoint for more concise configuration (see [Superset
OAuth2 configuration docs]). Using the OAuth2 implementation in FAB also gives
us automatic user creation and role synchronization, if we need it.
FAB comes with support for several OAuth2 providers (Facebook, Github, and
Authentik to name a few), but does not have built-in support for Authelia yet.
So we have to define a function that maps the user info from Authelia to the
`user_info` structure used by FAB. We do this by creating a custom
`SecurityManager`:
```Python
from superset.security import SupersetSecurityManager
import logging
logger = logging.getLogger(__name__)
class AutheliaSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None):
logger.debug("Oauth2 provider: {0}.".format(provider))
if provider == "authelia":
# Use the full userinfo endpoint URL from OIDC discovery
userinfo_endpoint = self.appbuilder.sm.oauth_remotes[
provider
].server_metadata["userinfo_endpoint"]
resp =
self.appbuilder.sm.oauth_remotes[provider].get(userinfo_endpoint)
data = resp.json()
logger.debug(data)
return {
"username": data.get("preferred_username", ""),
"first_name": data.get("given_name", ""),
"last_name": data.get("family_name", ""),
"email": data.get("email", ""),
"role_keys": data.get("groups", []),
}
return {}
```
Then use this security manager in `superset_config.py`:
```python
# ...
AUTH_TYPE = AUTH_OAUTH
OIDC_CLIENT_SECRETS = "/app/docker/pythonpath_dev/client_secret.json"
with open(OIDC_CLIENT_SECRETS) as f:
oidc_secrets = json.load(f)
authelia_secrets = oidc_secrets["authelia"]
remote_app = {
"client_id": authelia_secrets["client_id"],
"client_secret": authelia_secrets["client_secret"],
"server_metadata_url":
f"{authelia_secrets['issuer']}/.well-known/openid-configuration",
"token_endpoint_auth_method": "client_secret_post",
"scope": "openid email profile groups",
}
OAUTH_PROVIDERS = [
{
"name": "authelia",
"token_key": "access_token",
"icon": "fa-address-card",
"remote_app": remote_app,
}
]
CUSTOM_SECURITY_MANAGER = AutheliaSecurityManager
# These are all flask app builder settings:
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Gamma"
AUTH_ROLES_SYNC_AT_LOGIN = True
# Role mapping (optional - you can customize this based on your needs)
AUTH_ROLES_MAPPING = {
"superset_admin": ["Admin"],
"superset": ["Gamma"],
"custom_group": ["custom_matching_superset_role_A",
"custom_matching_superset_role_B"],
# ...
}
```
## Follow up
These are some ideas to follow-up:
- For some reason, `First Name` and `Last Name` fields are not filled in
Superset, although they are in the mapped user info
- Change this implementation to allow for multiple OAuth login providers
- Add Authelia OAuth2 support to FAB
- Add this integration guide to the list of [OIDC integrations in the Authelia
docs](https://www.authelia.com/integration/prologue/introduction/)
## Appendices
### Authelia configuration
I assume that you have Authelia setup and running, and that you know how to
generate secrets for your clients.
I use the following client configuration:
```yml
identity_providers:
oidc:
#...
clients:
# ...
- client_id: superset
client_name: Apache Superset
client_secret: '{{ fileContent "/config/secrets/oidc_superset_secret" |
trim }}'
public: false
authorization_policy: one_factor
redirect_uris:
- 'http://superset.dev.serraict.me/oauth-authorized/authelia'
- 'https://superset.dev.serraict.me/oauth-authorized/authelia'
- 'http://superset.dev.serraict.me/authorize'
- 'https://superset.dev.serraict.me/authorize'
- 'https://superset.dev.serraict.me/oidc_callback'
scopes:
- openid
- email
- profile
- groups
userinfo_signed_response_alg: none
token_endpoint_auth_method: client_secret_post
```
When generating the secret for superset, I also generate an OIDC
`client_secret.json`:
```json
{
"authelia": {
"issuer": "https://auth.dev.serraict.me",
"client_id": "superset",
"client_secret": "the_oidc_superset_secret",
"redirect_uris": [
"https://superset.dev.serraict.me/oidc_callback"
]
}
}
```
---
[LLDAP]: https://github.com/lldap/lldap
[Flask App Builder]: https://flask-appbuilder.readthedocs.io/en/latest/
[Authelia]: https://www.authelia.com/overview/prologue/introduction/
[Superset configuration docs]:
https://superset.apache.org/docs/configuration/configuring-superset/
[Superset OAuth2 configuration docs]:
https://superset.apache.org/docs/configuration/configuring-superset/#custom-oauth2-configuration
GitHub link: https://github.com/apache/superset/discussions/34054
----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]