Nextcloud OIDC with authentik
Abstract
Recently I’ve been wanting to set up a local Single Sign-On (SSO) for my homelab. There are a lot of great options for Identity and Access Management (IAM) systems, like Keycloak (which probably has the most enterprise support), Authelia, and VoidAuth (which seems really cool and promising).
I’ve chosen to go with authentik, however, for it’s flexibility and ease of use. I’ve really liked it thus far, from it’s easy deployment in Kubernetes to it’s easy customization with Terraform/OpenTofu.
In this blog post I thought I’d share how I setup OpenID Connect (OIDC) authentication for Nextcloud with authentik. And while it’s possible to use LDAP and SAML for Nextcloud (and with authentik), I think OIDC is by far the easiest solution.
One problem that OIDC sometimes have is that clients redirects users to the SSO instead of handling the authentication in the client application (Nextcloud, in this example). This has a tendency not to work with some mobile and desktop applications. This is however not a problem for Nextcloud, since its clients already authenticates users by redirecting them to the Nextcloud server in the web browser first. So, this solution works for both the Nextcloud desktop and mobile applications.
This guide assumes you already have working authentik and Nextcloud instances running.
Configuring authentik
Note: I’ll be configuring authentik with Terraform/OpenTofu, but it should be fairly easy to translate into doing it manually.
View full Terraform example...
// Groups
resource "authentik_group" "nextcloud-admins" {
name = "Nextcloud Admins"
}
resource "authentik_group" "nextcloud-users" {
name = "Nextcloud Users"
}
// Builtin property mappings
data "authentik_property_mapping_provider_scope" "default-oauth-email" {
name = "authentik default OAuth Mapping: OpenID 'email'"
}
data "authentik_property_mapping_provider_scope" "default-oauth-openid" {
name = "authentik default OAuth Mapping: OpenID 'openid'"
}
data "authentik_property_mapping_provider_scope" "default-oauth-profile" {
name = "authentik default OAuth Mapping: OpenID 'profile'"
}
// Property Mappings
resource "authentik_property_mapping_provider_scope" "nextcloud-profile" {
name = "Nextcloud Profile"
scope_name = "nextcloud"
expression = <<EOF
# Extract all groups the user is a member of
groups = [group.name for group in user.ak_groups.all()]
# In Nextcloud, administrators must be members of a fixed group called "admin".
if "Nextcloud Admins" in groups and "admin" not in groups:
groups.append("admin")
return {
"name": request.user.name,
"groups": groups,
# Set a quota by using the "nextcloud_quota" property in the user's attributes
"quota": user.group_attributes().get("nextcloud_quota", None),
# To connect an existing Nextcloud user, set "nextcloud_user_id" to the Nextcloud username.
"user_id": user.attributes.get("nextcloud_user_id", str(user.uuid)),
}
EOF
}
// Builtin certificate key pairs
data "authentik_certificate_key_pair" "authentik-self-signed-certificate" {
name = "authentik Self-signed Certificate"
}
// Builtin flows
data "authentik_flow" "default-provider-authorization-explicit-consent" {
slug = "default-provider-authorization-explicit-consent"
}
data "authentik_flow" "default-provider-invalidation-flow" {
slug = "default-provider-invalidation-flow"
}
// Provider
resource "authentik_provider_oauth2" "nextcloud" {
name = "Provider for Nextcloud"
signing_key = data.authentik_certificate_key_pair.authentik-self-signed-certificate.id
property_mappings = [
data.authentik_property_mapping_provider_scope.default-oauth-email.id,
data.authentik_property_mapping_provider_scope.default-oauth-openid.id,
data.authentik_property_mapping_provider_scope.default-oauth-profile.id,
authentik_property_mapping_provider_scope.nextcloud-profile.id
]
authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id
invalidation_flow = data.authentik_flow.default-provider-invalidation-flow.id
sub_mode = "user_uuid"
allowed_redirect_uris = [
{
matching_mode = "strict",
url = "https://nextcloud.mars.internal/apps/user_oidc/code",
},
{
matching_mode = "strict",
url = "https://nextcloud.eskilstuna.gay/apps/user_oidc/code",
}
]
}
// Application
resource "authentik_application" "nextcloud" {
name = "Nextcloud"
slug = "nextcloud"
group = var.application-groups.pim
protocol_provider = authentik_provider_oauth2.nextcloud.id
}
// Policy Bindings
resource "authentik_policy_binding" "nextcloud-admins-access" {
target = authentik_application.nextcloud.uuid
group = authentik_group.nextcloud-admins.id
order = 0
}
resource "authentik_policy_binding" "nextcloud-users-access" {
target = authentik_application.nextcloud.uuid
group = authentik_group.nextcloud-users.id
order = 0
}
Overview
Below is a list of what we need to do in authentik.
- Create a user group and an admin group
- Create a proprty mapping
- Create a OAuth2 provider
- Create an application
- Create policy bindings for the groups
Creating Groups
I’ve opted for the creating of two groups: “Nextcloud Users” and “Nextcloud Admins”. There’s nothing special about these, you can just create them under “Directory > Groups”.
resource "authentik_group" "nextcloud-admins" {
name = "Nextcloud Admins"
}
resource "authentik_group" "nextcloud-users" {
name = "Nextcloud Users"
}
Creating Property Mapping
If we want users in the “Nextcloud Admins” group to actually be administrators in Nextcloud, we need to create a property mapping, since Nextcloud has a built-in admin group called “admin”, which we need to map to our group.
You can create it under “Customization > Property Mappings” and it needs to be of type “Scope Mapping”. It should look something like this.
resource "authentik_property_mapping_provider_scope" "nextcloud-profile" {
name = "Nextcloud Profile"
scope_name = "nextcloud"
expression = <<EOF
# Extract all groups the user is a member of
groups = [group.name for group in user.ak_groups.all()]
# In Nextcloud, administrators must be members of a fixed group called "admin".
if "Nextcloud Admins" in groups and "admin" not in groups:
groups.append("admin")
return {
"name": request.user.name,
"groups": groups,
# Set a quota by using the "nextcloud_quota" property in the user's attributes
"quota": user.group_attributes().get("nextcloud_quota", None),
# To connect an existing Nextcloud user, set "nextcloud_user_id" to the Nextcloud username.
"user_id": user.attributes.get("nextcloud_user_id", str(user.uuid)),
}
EOF
}
Creating Provider
Create an OAuth2 provider called “Provider for Nextcloud” under “Applications > Providers”. Take note of the Client ID and Client Secret, since we’ll need them later.
You can use any Signing Key you want, I’ve just used the default self-signed one. Then we’ll use the regular, built-in property mappings for “email”, “openid” and “profile”.
The only thing special about this provider is that we also have to assign the property mapping we created in the previous step (“Nextcloud Profile”), and we’ll set the Subject mode to “Based on the User’s UUID”.
We’ll also set a Redirect URI for “https://nextcloud.example.com/apps/user_oidc/code”.
resource "authentik_provider_oauth2" "nextcloud" {
name = "Provider for Nextcloud"
signing_key = data.authentik_certificate_key_pair.authentik-self-signed-certificate.id
property_mappings = [
data.authentik_property_mapping_provider_scope.default-oauth-email.id,
data.authentik_property_mapping_provider_scope.default-oauth-openid.id,
data.authentik_property_mapping_provider_scope.default-oauth-profile.id,
authentik_property_mapping_provider_scope.nextcloud-profile.id
]
authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id
invalidation_flow = data.authentik_flow.default-provider-invalidation-flow.id
sub_mode = "user_uuid"
allowed_redirect_uris = [
{
matching_mode = "strict",
url = "https://nextcloud.mars.internal/apps/user_oidc/code",
},
{
matching_mode = "strict",
url = "https://nextcloud.eskilstuna.gay/apps/user_oidc/code",
}
]
}
You’ll notice that I have two Redirect URIs, but that’s just because I have two URIs that I use to reach my Nextcloud instance.
Creating Application
Create an application under “Applications > Applications” and set the provider to the one we created in the previous step.
resource "authentik_application" "nextcloud" {
name = "Nextcloud"
slug = "nextcloud"
protocol_provider = authentik_provider_oauth2.nextcloud.id
}
Creating Policy Bindings
This step isn’t strictly necessary. It just makes sure that only members of our groups, “Nextcloud Users” and “Nextcloud Admins” can authenticate in Nextcloud.
Just go to “Applications > Applications > Nextcloud” and click on “Policy / Group / User Bindings”, then click “Bind existing Policy / Group / User”, choose “Group” and then “Nextcloud Admins”, and set the order to “0”. Then do the same for “Nextcloud Users”.
resource "authentik_policy_binding" "nextcloud-admins-access" {
target = authentik_application.nextcloud.uuid
group = authentik_group.nextcloud-admins.id
order = 0
}
resource "authentik_policy_binding" "nextcloud-users-access" {
target = authentik_application.nextcloud.uuid
group = authentik_group.nextcloud-users.id
order = 0
}
Configuring Nextcloud
Overview
Below is a list of what we need to do in Nextcloud.
- Install the OpenID Connect user backend
- Configure the OpenID Connect user backend
Install the OIDC App
Go to “Apps” and install the OpenID Connect user backend.
Configure the OIDC App
Go to “Administration > OpenID Connect” and click the plus icon to register a new OIDC provider.
Client Configuration
Set the following values.
- Identifier: “authentik”
- Client ID: The Client ID from you Nextcloud provider in authentik
- Client Secret: The Client Secret from you Nextcloud provider in authentik
- Discovery Endpoint: “https://authentik.example.com/application/o/nextcloud/.well-known/openid-configuration”
- Scope: “email profile nextcloud openid”
Note: The “nextcloud” scope here comes from the scope mapping we created in authentik.
Attribute Mapping
Set the following attributes mappings.
- User ID mapping: “sub”
- Quota mapping: “quota”
- Groups mapping: “groups”
- Display name mapping: “name”
- Email mapping: “email”
Authentication and Access Control Settings
Ensure the following boxes are ticked/enabled.
- Use group provisioning
- Send ID token hint on logout
Note: Do not tick the “Use unique user ID” box. This will also make it so that every group name that comes from authentik is hashed in Nextcloud, which will also apply to the “admin” group. I.e, this will break the ability for any authentik user to become admin.
Troubleshooting
Local Addresses
If Nextcloud and authentik are running on the same local network, you might have to add this configuration parameter to your config.php file.
'allow_local_remote_servers' => true
This “allows remote servers with local addresses, e.g., in federated shares, webcal services, and more”.
TLS Verification
If you’re using TLS encryption for your authentik instance (which you should!), then you need to make sure that Nextcloud trusts it’s TLS certificate. If you’re not using self-signed certificates you can probably skip this section.
Nextcloud, unlike many applications, does not use the systems CA trust bundle by default. It uses it’s own, usually located at “/var/www/html/resources/config/ca-bundle.crt”.
On Linux Systems
If you’ve installed Nextcloud on a Linux system, then it might be easiest to just add your own CA certificates to the systems CA trust bundle.
# I'll assume you have your CA certificates in a file called ca.crt or ca.pem
# For Debian/Ubuntu systems
sudo cp ca.crt /usr/local/share/ca-certificates
sudo update-ca-certificates
# For SUSE systems
sudo cp ca.pem /etc/pki/trust/anchors
sudo update-ca-certificates
# For RHEL systems
sudo cp ca.pem /etc/pki/ca-trust/source/anchors
sudo update-ca-trust
Then copy the systems trust bundle to Nextcloud.
sudo cp /etc/ssl/certs/ca-certificates.crt /var/www/html/resources/config/ca-bundle.crt
In Kubernetes
If you’ve deployed Nextcloud in Kubernetes, like me, you might want mount a complete trust store directly to the correct path in the pod.
I’m using a trust-manger Bundle resource like this.
Note: Setting “useDefaultCAs: true” is very important if you’re overriding the default Nextcloud trust store. Otherwise Nextcloud will only trust your own CA.
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
name: home-internal-ca.crt
spec:
sources:
- useDefaultCAs: true # Important!
- inLine: |
-----BEGIN CERTIFICATE-----
MIIBwDCCAWagAwIBAgIRAIbgEVlTCwD5qhf58A6nudwwCgYIKoZIzj0EAwIwPjEZ
...
o3j37R6cxYayBsvmtn2EF9VyvJ0=
-----END CERTIFICATE-----
target:
configMap:
key: ca.crt
namespaceSelector:
matchLabels:
trust: enabled
Then I’m just setting the label “trust: enabled” on the namespace where Nextcloud is deployed. trust-manger will then create a ConfigMap resource called “home-internal-ca.crt” in the namespace.
apiVersion: v1
kind: Namespace
metadata:
name: nextcloud
labels:
kubernetes.io/metadata.name: nextcloud
trust: enabled
After that, I just mount the ConfigMap to the correct place in the pod.
Note that I’m using the Nextcloud Helm chart, so this goes in my values.yaml file, but you can easily do the same directly in a Deployment resource.
nextcloud:
extraVolumes:
- name: home-internal-ca
configMap:
name: home-internal-ca.crt
extraVolumeMounts:
# Mounting ConfigMap to systems trust store as well
# just for good measure
- name: home-internal-ca
mountPath: /etc/ssl/certs/ca-certificates.crt
subPath: ca.crt
readOnly: true
- name: home-internal-ca
mountPath: /var/www/html/resources/config/ca-bundle.crt
subPath: ca.crt
readOnly: true
Conclusion
You’re all done! Now you can add yourself or other uses to the “Nextcloud Users” or “Nextcloud Admins” groups in authentik and sign in with them in Nextcloud. If the users don’t already exist in Nextcloud they will be created automatically.
Related Reads
- https://integrations.goauthentik.io/chat-communication-collaboration/nextcloud/
- https://github.com/nextcloud/user_oidc