all systems self-hosted

~/selfhostjournal $

← All articles

Single sign-on for WordPress with Authentik

4–7 minutes

In short

Problem: WordPress was the last service still asking for its own password.

Solution: An OAuth2/OpenID provider in Authentik plus the OpenID Connect Generic plugin in WordPress, with every value listed below.

Tested with: WordPress 7.1, Authentik 2026.8, OpenID Connect Generic 3.11

I use Authentik as the single login for my self-hosted services. This blog should work the same way: one click on the login button, passkey, done. The normal WordPress login stays as a fallback, in case Authentik is ever down.

In the examples, Authentik runs at https://auth.example.com, the blog at https://blog.example.com, and the Authentik application slug is blog. Replace these with your own values.

How the login works

  1. You click the login button on the WordPress login page.
  2. WordPress sends you to Authentik, where you log in, for example with a passkey.
  3. Authentik sends you back to WordPress with a one-time code.
  4. WordPress exchanges the code for tokens in the background, reads your username and e-mail, and logs you in as the matching WordPress user.

1. Authentik: create the provider

In the Authentik admin interface go to Applications → Providers → Create and choose OAuth2/OpenID Provider. These are the settings I use:

SettingValue
NameProvider for Blog
Authorization flowdefault-provider-authorization-implicit-consent (no extra “Allow access?” screen)
Invalidation flowdefault-provider-invalidation-flow
Client typeConfidential
Client ID / Client Secretgenerated by Authentik, copy both for WordPress
Redirect URI (strict)https://blog.example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize
Signing keyauthentik Self-signed Certificate
Scopesthe default mappings for openid, email and profile
Subject modeBased on the User’s hashed ID
Include claims in id_tokenon
Issuer modeEach provider has a different issuer
Grant typesAuthorization code and refresh token
Access token validity5 minutes
Refresh token validity30 days

Watch out: if you create the provider through the Authentik API instead of the UI, set grant_types yourself. Without it the list stays empty and every login ends with “The request is otherwise malformed”. The Authentik server log then shows “Invalid grant_type for provider”.

For reference, the same provider created through the API:

curl -X POST https://auth.example.com/api/v3/providers/oauth2/ \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Provider for Blog",
    "client_type": "confidential",
    "authorization_flow": "<uuid of default-provider-authorization-implicit-consent>",
    "invalidation_flow": "<uuid of default-provider-invalidation-flow>",
    "property_mappings": ["<openid uuid>", "<email uuid>", "<profile uuid>"],
    "signing_key": "<uuid of the signing certificate>",
    "sub_mode": "hashed_user_id",
    "include_claims_in_id_token": true,
    "grant_types": ["authorization_code", "refresh_token"],
    "redirect_uris": [{
      "matching_mode": "strict",
      "url": "https://blog.example.com/wp-admin/admin-ajax.php?action=openid-connect-authorize"
    }]
  }'

2. Authentik: create the application

Go to Applications → Applications → Create:

SettingValue
NameBlog
Slugblog (it becomes part of the issuer and JWKS URLs)
ProviderProvider for Blog
Launch URLhttps://blog.example.com/wp-admin/

Then open the application, go to Policy / Group / User Bindings and bind the users or the group who may log in to WordPress. Without a binding, every Authentik user can use the application.

All endpoints are listed in the discovery document of the application. Open it once to check that it works:

curl -s https://auth.example.com/application/o/blog/.well-known/openid-configuration

3. WordPress: install the plugin

Install and activate OpenID Connect Generic (by daggerhart) from the plugin directory. The settings are under Settings → OpenID Connect Client. As soon as the plugin is active, the login page shows a button. It only works once the settings below are saved.

4. WordPress: all plugin settings

FieldValue
Login TypeOpenID Connect button on login form
Client IDfrom the Authentik provider
Client Secret Keyfrom the Authentik provider
OpenID Scopeopenid email profile
Login Endpoint URLhttps://auth.example.com/application/o/authorize/
Userinfo Endpoint URLhttps://auth.example.com/application/o/userinfo/
Token Validation Endpoint URLhttps://auth.example.com/application/o/token/
End Session Endpoint URLhttps://auth.example.com/application/o/blog/end-session/
JWKS URIhttps://auth.example.com/application/o/blog/jwks/
Issuer (if your version asks)https://auth.example.com/application/o/blog/
ACR valuesleave empty
Identity Keypreferred_username
Disable SSL Verifyoff
HTTP Request Timeout5
Nickname Keypreferred_username
Email Formatting{email}
Display Name Formatting{name}
Identify with User Nameoff
Enable Refresh Tokenon
Link Existing Userson
Create user if does not existoff
Redirect Back to Origin Pageon
Redirect to the login screen when session is expiredoff
Enable Loggingon while testing, off afterwards

Only the JWKS URI, the End Session URL and the issuer contain the application slug. The authorize, token and userinfo endpoints are the same for every application in Authentik.

Tip: the bottom of the plugin settings page shows the exact Redirect URI WordPress will use. Copy it from there into Authentik, so the two match character for character.

How existing users are matched

With Link Existing Users on, the plugin looks for a WordPress user whose username matches the value of the identity key, here the Authentik username (preferred_username). The first login links the two accounts, and after that the link is fixed. Use the same username in WordPress and Authentik, or log in once with the matching account. With Create user if does not exist off, nobody gets a new WordPress account just because they have an Authentik login.

Optional: keep the secret out of the database

Instead of typing the values into the settings page, you can define them in wp-config.php. The plugin then uses these values and shows the fields as read-only:

define( 'OIDC_CLIENT_ID', 'your-client-id' );
define( 'OIDC_CLIENT_SECRET', 'your-client-secret' );
define( 'OIDC_CLIENT_SCOPE', 'openid email profile' );
define( 'OIDC_ENDPOINT_LOGIN_URL', 'https://auth.example.com/application/o/authorize/' );
define( 'OIDC_ENDPOINT_USERINFO_URL', 'https://auth.example.com/application/o/userinfo/' );
define( 'OIDC_ENDPOINT_TOKEN_URL', 'https://auth.example.com/application/o/token/' );
define( 'OIDC_ENDPOINT_LOGOUT_URL', 'https://auth.example.com/application/o/blog/end-session/' );

5. Test it

Before clicking the button, test the authorize request from the command line. A working provider redirects to the Authentik login flow:

curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
  "https://auth.example.com/application/o/authorize/?response_type=code&scope=openid%20email%20profile&client_id=YOUR_CLIENT_ID&state=test&redirect_uri=https%3A%2F%2Fblog.example.com%2Fwp-admin%2Fadmin-ajax.php%3Faction%3Dopenid-connect-authorize"

# good:  302 -> https://auth.example.com/if/flow/default-authentication-flow/?...
# wrong: 302 -> https://blog.example.com/...?error=invalid_request&...

If that looks good, log out of WordPress, click the login button and log in with Authentik. You should land in the dashboard as your existing user.

Troubleshooting

What you seeLikely cause
“The request is otherwise malformed”No grant types on the provider (created through the API)
“Redirect URI Error”The redirect URI in Authentik differs from the one WordPress sends, even by a slash
Back on WordPress, but not logged inNo WordPress user matches preferred_username, and user creation is off
“Permission denied” in AuthentikYour user is not bound to the application
A blank page with “0”Authentik returned an error to the redirect URI. Check the Authentik server log.

What I learned

  • The Authentik UI fills in defaults that the API does not.
  • The server log tells you the real reason when the browser only shows a generic error.
  • Keep the local WordPress login as a fallback. If the identity provider is down, you still want to reach your own site.

Everything here worked in my setup at the time of writing. Software changes, and your setup is different: back up first, test carefully, and use the steps at your own risk. Details in the legal notice.