Previously, my home NAS ran a lot of apps, each with its own authentication method, which was a mess. I had to remember a lot of passwords.
The worst part was that many apps didn't support passkey authentication, rendering my Yubikey useless.
Moreover, this created a large attack surface; if one app was insecure, hackers could do a lot of damage.
So, to unify identity management for my home accounts, I decided to set up Authentik.

After setting it up, I only need to log in once, and all applications can be accessed. Permissions are centrally configured, making it clear who can do what. Logs are unified for auditing. Most importantly:
The attack surface has been reduced. Now, hackers attacking any application must first breach a single entry point, which is extremely secure.

And finally, I can use Passkey to log in to all apps! It's secure and convenient!

Which applications can be integrated?
OIDC
The easiest ones to integrate are those that already have OIDC support. They can be integrated natively.
In simple terms, OIDC (OAuth, OpenID Connect) means that the application itself is not responsible for identity authentication. Instead, when authentication is required, it redirects the user via a 302 to Authentik. Once the user has authenticated, they are redirected back with a Code. The application can then use this Code, along with the AppId and AppSecret, to retrieve the user's profile information.

As for how the specific application handles this—whether it merges the data, creates a new entry if none exists, asks the user for input, or even whether it stores the data in a database at all—these decisions are determined by the application's own business logic.
LDAP
LDAP is still used to pass passwords to specific applications. However, when the application verifies whether the password is correct, it accesses Authentik via the LDAP protocol to query and retrieve basic user information.
Although LDAP is not as lightweight or convenient, it can still be used as a viable option.
Forward Auth
If the application doesn't support any authentication mechanisms, I recommend using Forward Auth. Refer to this diagram:

The principle is that when each request comes in, it is first sent to the "gatekeeper" to check whether the request is legitimate.
The gatekeeper itself is a container deployed alongside your business application. It is provided by Authentik.
If the request is legitimate, it is reverse-proxied to the application; if not, the decision follows the gatekeeper's response. The gatekeeper will redirect the user with a 302 to the OpenID Connect flow.
For Forward Auth, I use Caddy as the reverse proxy. The configuration first requires running the gatekeeper, with the specific business application running alongside it. The gatekeeper handles communication with Authentik. The business application is unaware of the authentication process and simply receives an HTTP header after successful authentication. This is essentially a universal solution that works with any application.
version: '3.9'
services:
authentik_proxy:
image: hub.aiursoft.cn/ghcr.io/goauthentik/proxy:2025.6
environment:
AUTHENTIK_HOST: "https://auth.aiursoft.cn/"
AUTHENTIK_INSECURE: "false"
AUTHENTIK_TOKEN: "{{KOEL_OUTPOST_TOKEN}}"
networks:
- proxy_app
- internal
koel:
image: hub.aiursoft.cn/phanan/koel
volumes:
- music:/music
- artifacts:/artifacts
- covers:/var/www/html/public/img/covers
- search_index:/var/www/html/storage/search-indexes
- /swarm-vol/koel/config:/var/www/html/.env # This is a single file, not a directory
networks:
- internal
- proxy_app
# Now we have koel_koel:80
# And we have koel_authentik_proxy:9000
# Protected by Authentik
musics.aiursoft.cn {
log
import hsts
import rate_limit
header -content-security-policy
header -x-frame-options
encode br gzip
#Use Authentik for forward auth
route {
reverse_proxy /manifest.json http://koel_koel:80
reverse_proxy /outpost.goauthentik.io/* http://koel_authentik_proxy:9000 {
header_up Host {http.reverse_proxy.upstream.host}
}
forward_auth http://koel_authentik_proxy:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
}
reverse_proxy http://koel_koel:80
}
}
Reverse Proxy
The most straightforward approach is reverse proxying. It is also the most versatile solution. If you cannot use Caddy, you will have to rely on the gateway to reverse proxy your application. The gateway will handle identity authentication for you.
This method can easily cause the gateway to become a performance bottleneck. After all, all traffic flowing through the application must additionally pass through the gateway. However, the advantage is that it supports all scenarios and all types of applications.
Memory Mnemonic
Given that I have a large number of applications to configure, I had to first write down a mnemonic to help me remember their configuration methods. I organized this into a document before starting the configuration process.
This is quite troublesome because many of my applications already have a substantial user base. I need to handle user merging carefully. Different applications have different strategies for merging. For example, gitea asks the user how to merge, gist returns a direct 500 error, gitlab merges directly if email addresses match, and nextcloud merges if usernames match (which requires additional configuration), and so on. It is very complicated.
The mnemonic is as follows:
未登录、已登录、管理员
* 协议名称
* 传参方式
* 权限继承方式
* 开启角色管理
* 管理员组开启
* 角色信息字段
* 禁止登录框
* 合并用户方式
* 注销行为
The ASP.NET Core App I Developed
For the ASP.NET Core App that I develop myself, it is naturally the easiest. After all, the code is right in my hands, so I can implement it however I want without falling into any pitfalls.
Typically, I store the configuration in appsettings.json:
{
"AppSettings": {
// 可选值为 "Local" 或 "OIDC"
"AuthProvider": "Local"
},
"OIDC": {
"Authority": "https://your-oidc-provider.com",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret"
},
}
Then, you can consume these configurations at startup. Here, I also use the mediator to publish an event.
var authProvider = configuration.GetValue<string>("AppSettings:AuthProvider");
if (authProvider == "OIDC")
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/SignIn";
options.LogoutPath = "/auth/signout";
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
var oidcConfig = configuration.GetSection("OIDC");
options.Authority = oidcConfig["Authority"];
options.ClientId = oidcConfig["ClientId"];
options.ClientSecret = oidcConfig["ClientSecret"];
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false;
options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
options.TokenValidationParameters.RoleClaimType = "groups";
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = async context =>
{
var mediator = context.HttpContext.RequestServices.GetRequiredService<IMediator>();
Console.WriteLine("OnTokenValidated. Got claims:");
foreach (var c in context.Principal!.Claims)
{
Console.WriteLine($" {c.Type} => {c.Value}");
}
// 从OIDC的claims中提取关键信息
var name = context.Principal!.FindFirst(JwtRegisteredClaimNames.Name)?.Value
?? context.Principal!.FindFirst("name")?.Value;
if (string.IsNullOrEmpty(name))
{
context.Fail("Name claim not found in OIDC token.");
return;
}
// 创建或同步用户,并获取本地用户的UID
var uid = await mediator.Send(new OidcUserSyncCommand(name));
// 清除OIDC的旧claims,添加我们自己的claims
var identity = (ClaimsIdentity)context.Principal.Identity;
identity!.RemoveClaim(identity.FindFirst(ClaimTypes.NameIdentifier)); // 移除OIDC的sub
identity.AddClaim(new("uid", uid.ToString()));
// 我这个应用比较特殊,是单人的博客,只要登录了,我就赋予 Administrator 这个 role。大部分应用不需要
identity.AddClaim(new(ClaimTypes.Role, "Administrator"));
}
};
});
}
else // 默认为 "Local" 认证
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = "/auth/accessdenied";
options.LoginPath = "/auth/signin";
options.LogoutPath = "/auth/signout";
});
}
Finally, don't forget to create the user if they don't exist in the database.
// Command: 包含从OIDC获取的必要信息
public record OidcUserSyncCommand(string Username) : IRequest<Guid>;
// Handler: 处理用户同步的核心逻辑
public class OidcUserSyncCommandHandler(IRepository<LocalAccountEntity> repo)
: IRequestHandler<OidcUserSyncCommand, Guid>
{
public async Task<Guid> Handle(OidcUserSyncCommand request, CancellationToken ct)
{
// OIDC用户我们以Username为唯一标识
var account = await repo.GetAsync(p => p.Username == request.Username);
if (account is not null)
{
// 用户已存在,直接返回ID
return account.Id;
}
// 用户不存在,创建一个新用户
var newAccount = new LocalAccountEntity
{
Id = Guid.NewGuid(),
Username = request.Username,
CreateTimeUtc = DateTime.UtcNow,
PasswordHash = "OIDC_USER",
PasswordSalt = string.Empty
};
await repo.AddAsync(newAccount, ct);
return newAccount.Id;
}
}
That's it. When you're ready, just fill in the OIDC AppId, AppSecret, and Authority into the environment variables, and authentication will work as soon as you start the application.
You don't need to change a single line of code in the other Controllers. Continue to use [Authorize] for protection and use User.Identity?.Name to get the current username.
OpenWeb Chat
Unauthenticated users cannot use the service at all. Authenticated users can access almost all features, while administrators can manage advanced settings.
- Based on the OpenId Connect protocol.
- Client ID and Client Secret must be passed to the OpenWeb Chat service via environment variables.
- Permission information is inherited based on environment variables. Users with a specific
groupcan be added to the OpenWeb Chat administrator group.- Ensure that role management is enabled by setting the environment variable
ENABLE_OAUTH_ROLE_MANAGEMENT. - Specify which
groupusers can become OpenWeb Chat administrators using the environment variableOAUTH_ADMIN_ROLES. - Specify that the
groupsfield represents user role information using the environment variableOAUTH_ROLES_CLAIM. - Disable the OpenWeb Chat login form by setting the environment variable
ENABLE_LOGIN_FORM=False.
- Ensure that role management is enabled by setting the environment variable
- Users are automatically matched by Email when merging accounts.
- Logging out only ends the OpenWeb Chat session and does not affect the Authentik session.
Note: You must additionally configure the environment variable ENABLE_OAUTH_SIGNUP=True to allow users completing OAuth to automatically register with OpenWeb Chat.
Note: You must additionally configure the environment variable ENABLE_SIGNUP=False to disable the registration feature in OpenWeb Chat.
Jellyfin
Completely unusable when not logged in. Almost all features are available once logged in. Administrators can manage advanced settings.
- Based on the OpenId Connect protocol.
- Client ID and Client Secret must be passed to the Jellyfin service via in-app plugin configuration.
- Permission information is inherited based on plugin configuration. Users with a specific
groupcan be added to the Jellyfin administrator group.- Ensure the role management feature is enabled by setting the plugin configuration
Enable Authorization by Plugin. - Specify which
groupusers can become Jellyfin administrators by setting the plugin configurationAdmin Roles:jellyfin-admins. - Specify that the
groupsfield represents user role information by setting the plugin configurationRole Claim:groups.
- Ensure the role management feature is enabled by setting the plugin configuration
- Disable the Jellyfin login form by setting the Generic configuration
.manualLoginForm { display: none; }. - Users are automatically matched by username during user merging.
- Logging out only terminates the Jellyfin session and does not affect the Authentik session.
Note: You must additionally configure the plugin setting Scheme Override:https for OAuth to work properly.
Note: You must additionally inject a piece of JavaScript on the login page to make the Android client work:
// 替换为你自己的 Jellyfin OIDC 登录地址
const SSO_AUTH_URL = 'https://media.aiursoft.cn/sso/OID/start/Aiursoft';
// Self-executing function that waits for the document body to be available
(function waitForBody() {
// If document.body doesn't exist yet, retry in 100ms
if (!document.body) {
return setTimeout(waitForBody, 100);
}
/**
* Determines if the current page is a login page by checking multiple indicators
* @returns {boolean} True if this appears to be a login page
*/
function isLoginPage() {
const hash = location.hash.toLowerCase();
const pathname = location.pathname.toLowerCase();
// Check for URL patterns that typically indicate login pages
const hasLoginUrl = (
hash === '' ||
hash === '#/' ||
hash === '#/home' ||
hash === '#/login' ||
hash.startsWith('#/login') ||
pathname.includes('/login')
);
// Check for DOM elements that indicate a login form is present
const hasLoginElements = (
document.querySelector('input[type="password"]') !== null ||
document.querySelector('.loginPage') !== null ||
document.querySelector('#txtUserName') !== null
);
return hasLoginUrl || hasLoginElements;
}
/**
* Checks if the current page should be excluded from SSO button insertion
* These are typically pages where users are already authenticated
* @returns {boolean} True if this page should be excluded
*/
function shouldExcludePage() {
const hash = location.hash.toLowerCase();
// List of page patterns where we don't want to show the SSO button
const excludePatterns = [
'#/dashboard',
'#/home.html',
'#/movies',
'#/tv',
'#/music',
'#/livetv',
'#/search',
'#/settings',
'#/wizardstart',
'#/wizardfinish',
'#/mypreferencesmenu',
'#/userprofile'
];
return excludePatterns.some(pattern => hash.startsWith(pattern));
}
/**
* Initializes the OAuth device ID in localStorage if it doesn't exist
* This is required for Jellyfin native apps to maintain device identification
*/
function oAuthInitDeviceId() {
// Only set device ID if it's not already set and we're in a native shell environment
if (!localStorage.getItem('_deviceId2') && window.NativeShell?.AppHost?.deviceId) {
localStorage.setItem('_deviceId2', window.NativeShell.AppHost.deviceId());
}
}
/**
* Creates and inserts the SSO login button into the login page
* Only runs if we're on a valid login page and the button doesn't already exist
*/
function insertSSOButton() {
// Safety check: ensure we're on the right page before proceeding
if (!isLoginPage() || shouldExcludePage()) return;
// Try to find a suitable container for the SSO button
const loginContainer = document.querySelector('.readOnlyContent') ||
document.querySelector('form')?.parentNode ||
document.querySelector('.loginPage') ||
document.querySelector('#loginPage');
// Exit if no container found or button already exists
if (!loginContainer || document.querySelector('#custom-sso-button')) return;
// Skip insertion for Jellyfin Media Player (JMP) as it may have different auth handling
const isJMP = navigator.userAgent.includes("JellyfinMediaPlayer");
if (isJMP) return;
// Create the SSO button element
const button = document.createElement('button');
button.id = 'custom-sso-button';
button.className = 'raised block emby-button button-submit';
// Style the button to match Jellyfin's design while being visually distinct
button.style = 'display: flex; align-items: center; justify-content: center; gap: 10px; padding: 12px 20px; font-size: 16px; background-color: #3949ab; color: white; margin-top: 16px;';
// Add icon and text content
button.innerHTML = '<span class="material-icons">shield</span><span>Login with SSO</span>';
// Handle button click - prevent form submission and redirect to SSO
button.onclick = function (e) {
e.preventDefault();
oAuthInitDeviceId(); // Ensure device ID is set before SSO redirect
window.location.href = SSO_AUTH_URL;
};
// Add the button to the login container
loginContainer.appendChild(button);
}
// Initial setup: Check if we should insert the SSO button when script first loads
if (isLoginPage() && !shouldExcludePage()) {
// Delay insertion slightly to ensure all page elements are fully loaded
setTimeout(insertSSOButton, 500);
}
// Set up a MutationObserver to watch for dynamic page changes
// This handles cases where Jellyfin loads content dynamically via JavaScript
const observer = new MutationObserver(() => {
if (isLoginPage() && !shouldExcludePage()) {
// Check if login elements are ready and button hasn't been inserted yet
const ready = document.querySelector('.readOnlyContent') ||
document.querySelector('form') ||
document.querySelector('.loginPage');
if (ready && !document.querySelector('#custom-sso-button')) {
insertSSOButton();
}
}
});
// Start observing changes to the entire document body and its children
observer.observe(document.body, { childList: true, subtree: true });
// Listen for hash changes (when navigating between pages in Jellyfin's SPA)
window.addEventListener('hashchange', () => {
// Small delay to allow page transition to complete
setTimeout(() => {
if (isLoginPage() && !shouldExcludePage()) {
insertSSOButton();
}
}, 300);
});
})();
Using this plugin, you can inject this JavaScript: https://github.com/johnpc/jellyfin-plugin-custom-javascript
OpenGist
You can browse anonymously without logging in. Once logged in, you can use almost all features. Administrators can manage advanced settings.
- Based on the OpenID Connect protocol.
- The Client ID and Client Secret must be passed to the OpenGist service via environment variables.
- Permission information is inherited from environment variables. Users with a specific
groupcan be added to the OpenGist administrator group.- Role management is enabled by default.
- Use the environment variable
OG_OIDC_ADMIN_GROUPto specify whichgroupusers can become OpenGist administrators. - Use the environment variable
OG_OIDC_GROUP_CLAIM_NAMEto specify that thegroupsfield represents user role information. - Use the built-in OAuth2 configuration
Disable login formto disable the OpenGist login form. - Merging is not supported. Old users must be deleted manually. I have appended
_disabledto the usernames of all old users. - Logging out only terminates the OpenGist session; it does not affect the Authentik session.
Note: You must additionally configure Disable signup to off in the admin center to ensure that users who complete OAuth can automatically register with OpenGist.
Gitea
Unauthenticated users can browse anonymously, authenticated users can access almost all features, and administrators can manage advanced settings.
- Based on the OpenID Connect protocol.
- The Client ID and Client Secret must be passed to the Gitea service via the application's built-in OAuth2 configuration.
- Permission information is inherited based on the application's built-in OAuth2 configuration. Users with a specific
groupcan be added to the Gitea administrator group.- Ensure that role management is enabled based on the application's built-in OAuth2 configuration
Claim name providing group names for this source. (Optional). - Specify which users from a
groupcan become Gitea administrators based on the application's built-in OAuth2 configurationGroup Claim value for administrator users. (Optional - requires claim name above). - Specify that the
groupsfield represents user role information based on the application's built-in OAuth2 configurationClaim name providing group names for this source. (Optional). - Disable the Gitea sign-in form by setting the environment variables
ENABLE_PASSWORD_SIGNIN_FORM = falseandENABLE_OPENID_SIGNIN = false.
- Ensure that role management is enabled based on the application's built-in OAuth2 configuration
- Users are automatically matched by Email during user merging.
- Signing out only terminates the Gitea session and does not affect the Authentik session.
Note: Additional environment variables for the application need to be configured:
[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = true
ALLOW_ONLY_EXTERNAL_REGISTRATION = true
ENABLE_CAPTCHA = true
CAPTCHA_TYPE = image
REQUIRE_SIGNIN_VIEW = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.localhost
ENABLE_PASSWORD_SIGNIN_FORM = false
ENABLE_PASSKEY_AUTHENTICATION = false
[lfs]
PATH = /data/git/lfs
[mailer]
ENABLED = false
[openid]
ENABLE_OPENID_SIGNIN = false
ENABLE_OPENID_SIGNUP = false
[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
ENABLE_AUTO_REGISTRATION = true
ACCOUNT_LINKING = auto
To implement anonymous browsing, registration without confirmation, registration without email notification, registration disabled, password login disabled, external OpenID login disabled, external OAuth login disabled, automatic registration of OAuth-completed users, and automatic merging of OAuth-completed users.
Koel
OIDC is not supported at all; protection relies entirely on Forward Auth.
sequenceDiagram
User->>+Caddy: I want to access app!
Caddy->>+Outpost: Is this request valid?
Outpost->>+Caddy: No. No cookie found. 302 to Auth!
Caddy->>+User: 302 to Auth center!
User->>+Auth: I want to login!
Auth->>+User: Enter your passkey!
User->>+Auth: Here is my passkey!
Auth->>+User: Okay. You are logged in! 302 to Outpost with Code!
User->>+Caddy: I have a Code! Take me to Outpost!
Caddy->>+Outpost: He finished auth! He has a code!
Outpost->>+Auth: What's his information? Here is the code and secret!
Auth->>+Outpost: Here is his info..
Outpost->>+Outpost: Create user. Save in database.
Outpost->>+Caddy: A cookie for outpost.
Caddy->>+User: Set a cookie for outpost. 302 to app.
User->>+Caddy: I want to access the app. With my cookie.
Caddy->>+Outpost: Is this cookie valid?
Outpost->>+Caddy: Yes. A valid cookie. 200!
Caddy->>+App: +HTTP Header: User ID
App->>+App: Got header. Create user in database.
App->>+Caddy: App experience
Caddy->>+User:App experience
If not logged in, the service is completely unusable. Once logged in, almost all features are available.
- Based on the Forward Auth protocol.
- User identity is verified through Caddy's Forward Auth module.
- Koel uses IP addresses to ensure that only requests from Caddy are accepted.
- Koel retrieves user information via HTTP headers.
- Permission management is not supported. All users have the User role.
- User merging is not supported. Old users must be deleted manually.
- Logging out only terminates the Koel session; it does not affect the Authentik session.
Note: Additional environment variables must be configured for the application:
PROXY_AUTH_ENABLED=true
PROXY_AUTH_ALLOW_LIST=0.0.0.0/0,::/0 # Actual IP of Caddy
PROXY_AUTH_USER_HEADER=X-Authentik-Uid
PROXY_AUTH_PREFERRED_NAME_HEADER=X-Authentik-Username
GitLab
Unauthenticated users can browse anonymously, authenticated users can use nearly all features, and administrators can manage advanced settings.
- Based on the OpenID Connect protocol.
- The Client ID and Client Secret must be passed to the GitLab service via the application's built-in configuration file.
- Permission information is inherited from the application's built-in configuration file. Users belonging to a specific
groupcan be added to the GitLab administrator group.- Role management is enabled by default.
- The
gitlab_rails['omniauth_providers']setting in the application's built-in configuration file specifies whichgroupmembers can become GitLab administrators.- The
gitlab_rails['omniauth_providers']setting in the application's built-in configuration file specifies that thegroupsfield represents user role information. - The
gitlab_rails['omniauth_allow_single_sign_on']setting in the application's built-in configuration file is used to disable the GitLab login form.
- The
- Users are automatically matched by Email address during user merging.
- Logging out only terminates the GitLab session and does not affect the Authentik session.
Note: You need to additionally configure the application's environment variables:
gitlab_rails['omniauth_allow_single_sign_on'] = ['openid_connect']
gitlab_rails['omniauth_sync_email_from_provider'] = 'openid_connect'
gitlab_rails['omniauth_sync_profile_from_provider'] = ['openid_connect']
gitlab_rails['omniauth_sync_profile_attributes'] = ['email']
gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'openid_connect'
gitlab_rails['omniauth_block_auto_created_users'] = false
# gitlab_rails['omniauth_auto_link_ldap_user'] = false
# gitlab_rails['omniauth_auto_link_saml_user'] = false
gitlab_rails['omniauth_auto_link_user'] = ['openid_connect']
# gitlab_rails['omniauth_external_providers'] = ['twitter', 'google_oauth2']
gitlab_rails['omniauth_allow_bypass_two_factor'] = ['openid_connect']
gitlab_rails['omniauth_providers'] = [
{
name: 'openid_connect',
label: 'Aiursoft Login',
args: {
name: 'openid_connect',
scope: ['openid','profile','email'],
response_type: 'code',
issuer: 'https://auth.aiursoft.cn/application/o/gitlab/',
discovery: true,
client_auth_method: 'query',
uid_field: 'preferred_username',
send_scope_to_token_endpoint: 'true',
pkce: true,
client_options: {
identifier: '<id>',
secret: '<secret>',
redirect_uri: 'https://gitlab.aiursoft.cn/users/auth/openid_connect/callback',
gitlab: {
groups_attribute: "groups",
admin_groups: ["gitlab-admins"]
}
}
}
}
]
Nextcloud
Without logging in, no functionality is available. Once logged in, almost all features can be used, and administrators can manage advanced settings.
- Based on the OpenID Connect protocol.
- The Client ID and Client Secret must be passed to the Nextcloud service via the built-in plugin configuration.
- Configuration-based permission inheritance is used. Users with specific attributes can be added to the Nextcloud admin group.
- Nextcloud's role management is unique; only users whose
groupsattribute containsadminare recognized as administrators. - You can use the Python snippet below for attribute mapping to ensure that only members of the
nextcloud-adminsgroup are granted admin privileges. - Use the command
sudo docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:app:set --value=0 user_oidc allow_multiple_user_backendsto disable the Nextcloud login form.
- Nextcloud's role management is unique; only users whose
- When merging users, Nextcloud compares only the attributes you have configured against existing usernames.
- First, we need to collect the
user_idof each existing Nextcloud user and map it to thenextcloud_user_idattribute in the Autentik user table. - Then, add a Property Mapping to map
nextcloud_user_idto theuser_idattribute visible to Nextcloud. If this attribute does not exist, display theusernameto Nextcloud. - In Nextcloud's
Attribute Mapping, set the mapping forUser ID mappingtouser_id. - In Nextcloud's
Attribute Mapping, set the mapping forquotatoquota, and set the default value to200Gin Authentik. - In Nextcloud's
Attribute Mapping, set the mapping forGroupstogroups. - Prohibited: Nextcloud by default gives every user a unique user ID that is a hashed value of the provider and user ID. This can be turned off, but the uniqueness of users across multiple user backends and providers is no longer preserved then.
- Prohibited: Nextcloud to keep IDs in plain text, but also preserve their uniqueness across multiple providers, a prefix with the provider's name is added.
- Set
'allow_local_remote_servers' => trueinconfig.phpto allow Nextcloud to access the Authentik API. - In the Authentik Provider settings, set Subject Mode to Based on the User's UUID
- First, we need to collect the
# Extract all groups the user is a member of
groups = [group.name for group in user.ak_groups.all()]
# If the user is in 'nextcloud-admins', remove it and replace with 'admin'
if "nextcloud-admins" in groups:
groups.remove("nextcloud-admins")
if "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", "200G"),
# To connect an existing Nextcloud user, set "nextcloud_user_id" to the Nextcloud username.
"user_id": user.attributes.get("nextcloud_user_id", str(user.username)),
}
After completing the configuration above, when a user logs in to Nextcloud through Authentik, Nextcloud uses the nextcloud_user_id attribute to identify the user and maps it to the Nextcloud user ID. If the attribute does not exist, a new Nextcloud user is created, and nextcloud_user_id is used as its ID.
If the nextcloud_user_id attribute is not set for the user, Nextcloud uses username as the user ID.
Summary
Integrating OIDC into applications is incredibly rewarding. It’s satisfying to watch your app ecosystem grow richer and richer. On top of that, users can access your new services without having to register again and again—a seamless, pain-free experience.
The downside is that it can be quite a hassle. Many applications require you to handle attribute mapping yourself and carefully merge real user identities. In most cases, this means manually checking each user one by one.
My advice: if you’re in the early stages and your user base is still small, get Authentik set up as soon as possible. As long as you have fewer than 200 users, manual management is still feasible. But once your user count grows and you add more applications, merging identities can become a nightmare.
I deeply regret not setting up Authentik sooner. Because of the delay, we ended up with a large number of users who had registered with different email addresses and usernames across various platforms. Merging these identities has been extremely painful, and I’ve had to communicate with each of them individually. Right now, I can only integrate one app per day.
有个代码块缩进前冗余太多 有个代码块没有匹配到语言,没有染色