> ## Content Index
> Fetch the complete content index at: https://blog.coopnet.us/llms.txt
> Use this file to discover other available public pages before exploring further.

# Your Authentik login dies on 'not all parts available' — and two different bugs produce it
- URL: https://blog.coopnet.us/authentik-oidc-not-all-parts-available/
- Published: 2026-08-02T06:00:00.000Z
- Updated: 2026-10-18T02:30:45.000Z
- Description: Forgejo 500s on the OIDC callback. You fix the missing openid scope, retry, and get the identical error — because an unrelated misconfiguration throws the same one. Here is how to tell them apart by counting dots.
- Author: Renstur

I wired [Forgejo](https://forgejo.org/?ref=blog.coopnet.us) up to [Authentik](https://goauthentik.io/?ref=blog.coopnet.us) for single sign-on. The redirect worked, Authentik authenticated me, the browser came back to the callback with a valid `?code=` — and Forgejo threw a 500:

```
UserSignIn: oauth2: error decoding JWT token:
jws: invalid token received, not all parts available

```

That message is about as unhelpful as it gets. It names no field, no scope, no key. And it is thrown by **two completely unrelated misconfigurations** — so you can find the first one, fix it correctly, retry, and watch the exact same error come back. At which point the natural conclusion is that your fix was wrong.

It wasn't. There was just a second one behind it.

## What the error actually means

Forgejo (and Gitea) sign in through goth's OpenID Connect provider, which decodes the `id_token` with roughly this:

```go
jwtParts := strings.Split(jwt, ".")
if len(jwtParts) != 3 {
    return nil, errors.New("jws: invalid token received, not all parts available")
}

```

That is the whole test. **Exactly three dot-separated parts, or this error.** So the message really means: *what I got where an id\_token should be was not a three-part JWS.* It could be empty. It could be something else entirely. The error does not distinguish, which is precisely the problem.

Two things commonly land you outside "three parts."

## Cause 1: the `openid` scope isn't actually being granted

Without the `openid` scope, the request is not an OIDC request at all — it's plain OAuth2\. The provider returns an `access_token` and **no `id_token`**. Forgejo tries to decode an empty string, gets one part, and throws.

This needs to be right in **two** places, and people usually only check one:

1. **The client** must request it. In Forgejo, Site Administration → Authentication Sources → your source → *Additional Scopes* → `openid email profile`. If you typed custom scopes and left `openid` out, you never asked.
2. **The Authentik provider** must be willing to grant it. The provider's *Scopes* property mappings must include **`authentik default OAuth Mapping: OpenID 'openid'`**.

Miss the second and Authentik does something quietly hostile: it intersects what you asked for with what it allows, drops the rest, and proceeds. It tells you, but only in its own log:

```json
{
  "event": "Application requested scopes not configured, setting to overlap",
  "scope_allowed": "{'email', 'profile'}",
  "scope_given":   "{'email', 'profile', 'openid'}"
}

```

There's your smoking gun. Search `authentik-server` logs for `setting to overlap`.

### The check that will lie to you

Here is the part that cost me real time. The obvious way to verify the provider's scopes is the discovery document:

```
GET https://auth.example.com/application/o/<slug>/.well-known/openid-configuration

```

I pulled it, saw this, and declared Authentik healthy:

```json
"scopes_supported": ["openid", "profile", "email"]

```

**It was not healthy.** `scopes_supported` advertises `openid` regardless of whether the mapping is assigned to that provider — it's OIDC's mandatory scope, so it's always listed. Meanwhile `scope_allowed` was `{email, profile}`. The discovery doc and the actual behaviour disagreed, and I trusted the wrong one.

Verify against the provider's assigned property mappings, or against the log line above. Not discovery.

## Cause 2: the provider has an Encryption Key set

Fix the scope, retry, same error. This is where you start doubting yourself.

An Authentik OAuth2 provider has two key fields, and they do very different things:

- **Signing Key** — signs the token. Produces a **JWS**: `header.payload.signature`, three parts.
- **Encryption Key** — *encrypts* the token. Produces a **JWE**: `header.encrypted_key.iv.ciphertext.tag`, **five parts**.

Set an Encryption Key and Authentik stops issuing signed tokens and starts issuing encrypted ones. Five parts. `len(jwtParts) != 3`. Same error, entirely different cause.

Forgejo and Gitea have **no JWE support at all**. Neither do most self-hosted apps — Grafana, Portainer, and friends all expect a plain signed JWT. No amount of scope fiddling will ever fix this one.

Mine had both fields set to the same certificate, which is an easy thing to do when a dropdown sits directly under another dropdown you just filled in correctly.

## The ten-second diagnostic: count the dots

You don't have to guess which cause you have. Count the separators in an issued token. If you run Authentik with Postgres:

```sql
SELECT a.expires,
       a._scope,
       length(a.token) - length(replace(a.token, '.', '')) AS dots
FROM authentik_providers_oauth2_accesstoken a
JOIN authentik_providers_oauth2_oauth2provider o
  ON o.provider_ptr_id = a.provider_id
WHERE o.client_id = '<your client id>'
ORDER BY a.expires DESC
LIMIT 5;

```

Read it like this:

| dots  | meaning       | action                       |
| ----- | ------------- | ---------------------------- |
| **2** | 3 parts — JWS | token shape is fine          |
| **4** | 5 parts — JWE | clear the **Encryption Key** |

And `_scope` tells you about cause 1 in the same row. If `openid` is missing there, that's your problem — regardless of what discovery claims.

Before my fix, the newest row read `email profile` and `dots=4`: both bugs, visible in one query. After: `email profile openid` and `dots=2`.

## The fix

- **Authentik → Providers → your provider → Advanced protocol settings → Encryption Key → blank.** Leave Signing Key alone. You want signed, not encrypted; the channel is already TLS-protected, and encrypted id\_tokens are for a narrow set of clients that specifically support them.
- **Provider *Scopes*** must include the `openid` mapping.
- **Forgejo's Additional Scopes**: `openid email profile`.

One nicety: editing the auth source in Forgejo's web UI re-registers the provider **live** — no container restart. You can confirm what it's really sending by reading the redirect it generates:

```bash
curl -sS -D - http://<forgejo>:3000/user/oauth2/<source-name> | grep -i '^location:'

```

The `scope=` in that URL is the truth, not whatever the database says.

## The takeaway

When one error string has multiple causes, fixing one cause looks *exactly* like fixing nothing. That's the trap — not the individual misconfigurations, which are both mundane, but the way the identical symptom hides the fact that you're making progress.

The defence is to stop reasoning from the error and find a signal with more resolution. Here that was two of them: Authentik's own `setting to overlap` log line, and a dot count that distinguishes JWS from JWE in one integer. Both take seconds once you know they exist, and either one would have skipped the entire detour.

Also: when a status endpoint agrees that everything is fine and the behaviour disagrees, believe the behaviour. `scopes_supported` was not lying, exactly — it was answering a different question than the one I was asking it.

---

*Hostnames, paths, identifiers and product-specific strings in this post are illustrative. The failure modes, commands and fixes are real.*