OpenID

Vikunja allows for authentication with an external identity source such as Authentik, Keycloak or similar via the OpenID Connect standard.

OpenID Connect Overview #

OpenID Connect is a standardized identity layer built on top of the more generic OAuth 2.0 specification, simplifying interaction between the involved parties significantly. While the OpenID specification is worth a read, we summarize the most important basics here.

The involved parties are:

  • Resource Owner: typically the end-user
  • Resource Server: the application server handling requests from the client, the Vikunja API in our case
  • Client: the application or client accessing the RS on behalf of the RO. Vikunja web frontend or any of the apps
  • Authorization Server: the server verifying the user identity and issuing tokens. These docs also use the words OAuth 2.0 provider, Identity Provider interchangeably.

After the user is authenticated, the provider issues a token to the user, containing various claims. There's different types of tokens (ID token, access token, refresh token), and all of them are created as JSON Web Token. Claims in turn are assertions containing information about the token bearer, usually the user.

Scopes are requested by the client when redirecting the end-user to the Authorization Server for authentication, and indirectly control which claims are included in the resulting tokens. There's certain default scopes, but its also possible to define custom scopes, which are used by the feature assigning users to Teams automatically.

Supported and required claims #

Vikunja only requires a few claims to be present in the ID token to successfully authenticate the user. Additional claims can be added though to customize behaviour during user creation.

The following table gives an overview about the claims supported by Vikunja. The scope column lists the scope that should request the claim according to the OpenID Connect Standard. It omits the claims such as sub or issuer required by the openid scope, which must always be present.

ClaimTypeScopeComment
emailrequiredemailSets the email address of the user. Taken from the userinfo endpoint if not present in ID token. User creation fails if claim not present and userinfo lookup fails.
nameoptionalprofileSets the display name of the user. Taken from the userinfo endpoint if not present in ID token.
preferred_usernameoptionalprofileSets the username of the user. Taken from the userinfo endpoint if not present in ID token. If this also doesn't contain the claim, use the nickname claim from userinfo instead. If that one is not available either, the username is auto-generated by Vikunja.
vikunja_groupsoptionalN/ACan be used to automatically assign users to teams. See below for a more detailed explanation about the expected format and implementation examples.

If one of the claims email, name or preferred_username is missing from the ID token, Vikunja will attempt to query the userinfo endpoint to obtain the information from there.

Configuring OIDC Authentication #

To achieve authentication via an external provider, it is required to (a) configure a confidential Client on your OAuth 2.0 provider and (b) configure Vikunja to authenticate against this provider. Example configurations are provided for various different identity providers, below you can find generic guides though.

OpenID Connect defines various flow types indicating how exactly the interaction between the involved parties work, Vikunja makes use of the standard Authorization Code Flow.

Step 1: Configure your Authorization Server #

The first step is to configure the Authorization Server to correctly handle requests coming from Vikunja. In general, this involves the following steps at a minimum:

  • Create a confidential client and obtain the client ID and client secret
  • Configure (whitelist) redirect URLs that can be used by Vikunja
  • Make sure the required scopes (openid profile email are the default scopes used by Vikunja) are supported
  • Optional: configure an additional scope for automatic team assignment, see below for details

More detailed instructions for various different identity providers can be found here

Step 2: Configure Vikunja #

Vikunja has to be configured to use the identity provider. Please note that there is currently no option to configure these settings via environment variables, they have to be defined using the configuration file. The configuration schema is as follows:

auth:
  openid:
    enabled: true
    redirecturl: https://vikunja.mydomain.com/auth/openid/  <---- slash at the end is important
    providers:
      - name: <provider-name>
        authurl: <auth-url>  <----- Used for OIDC Discovery, usually the issuer 
        clientid: <vikunja client-id>
        clientsecret: <vikunja client-secret>
        scope: openid profile email

The value for authurl can be obtained from the metadata of your provider. Note that the authurl is used for OIDC Discovery. Typically, you'll want to use the issuer URL as found in the provider metadata.

The values for clientid and clientsecret are typically obtained when configuring the client. The scope usually doesn't need to be specified or changed, unless you want to configure the automatic team assignment.

Optionally it is possible to disable local authentication and therefore forcing users to login via OpenID connect:

auth:
  local:
    enabled: false

Automatically assign users to teams #

Starting with version 0.24.0, Vikunja is capable of automatically adding users to a team based on OIDC claims added by the identity provider. If configured, Vikunja will sync teams, automatically create new ones and make sure the members are part of the configured teams. Teams which exist only because they were created from oidc attributes are not editable in Vikunja.

To distinguish between teams created in Vikunja and teams generated automatically via oidc, generated teams have an oidcID assigned internally. Within the UI, the teams created through OIDC get a (OIDC) suffix to make them distinguishable from locally created teams.

On a high level, you need to make sure that the ID token issued by your identity provider contains a vikunja_groups claim, following the structure defined below. It depends on the provider being used as well as the preferences of the administrator how this is achieved. Typically you'd want to request an additional scope (e.g. vikunja_scope) which then triggers the identity provider to add the claim. If the vikunja_groups is part of the ID token, Vikunja will start the procedure and import teams and team memberships.

The minimal claim structure expected by Vikunja is as follows:

{
    "vikunja_groups": [
        {
            "name": "team 1",
            "oidcID": 33349
        },
        {
            "name": "team 2",
            "oidcID": 35933
        }
    ]
}

It is also possible to pass the description and the isPublic flag as optional parameters. If not present, the description will be empty and project visibility defaults to false.

{
    "vikunja_groups": [
        {
            "name": "team 3",
            "oidcID": 33349,
            "description": "My Team Description",
            "isPublic": true
        },
    ]
}

For each team, you need to define a team name and an oidcID, where the oidcID can be any string with a length of less than 250 characters. The oidcID is used to uniquely identify the team, so please make sure to keep this unique.

Below you'll find two example implementations for Authentik and Keycloak. If you've successfully implemented this with another identity provider, please let us know and submit a PR to improve the docs.

Setup in Authentik #

To configure automatic team management through Authentik, we assume you have already set up Authentik as an OIDC provider for authentication with Vikunja.

To use Authentik's group assignment feature, follow these steps:

  1. Edit your config to include the following scopes: openid profile email vikunja_scope
  2. Open <your authentik url>/if/admin/#/core/property-mappings
  3. Create a new property mapping called vikunja_scope as scope mapping. There is a field expression to enter python expressions that will be delivered with the oidc token.
  4. Write a small script like the following to add group information to vikunja_scope:
groupsDict = {"vikunja_groups": []}
for group in request.user.ak_groups.all():
  groupsDict["vikunja_groups"].append({"name": group.name, "oidcID": group.num_pk})
return groupsDict
  1. In Authentik's menu on the left, go to Applications > Providers > Select the Vikunja provider. Then click on "Edit", on the bottom open "Advanced protocol settings", select the newly created property mapping under "Scopes". Save the provider.

Now when you log into Vikunja via Authentik it will show you a list of scopes you are claiming. You should see the description you entered on the OIDC provider's admin area.

Proceed to Vikunja and open the teams page in the sidebar menu. You should see "(OIDC)" written next to each team you were assigned through OIDC.

Setup in Keycloak #

The kind people from Makerspace Darmstadt e.V. have written a guide on how to create a mapper for Vikunja here.

Use cases #

All examples assume one team called "Team 1" to be configured within your provider.

  • Token delivers team.name +team.oidcID and Vikunja team does not exist:
    New team will be created called "Team 1" with attribute oidcID: "33929"
  1. In Vikunja Team with name "team 1" already exists, but has no oidcID set:
    new team will be created called "team 1" with attribute oidcID: "33929"

  2. In Vikunja Team with name "team 1" already exists, but has different oidcID set:
    new team will be created called "team 1" with attribute oidcID: "33929"

  3. In Vikunja Team with oidcID "33929" already exists, but has different name than "team1":
    new team will be created called "team 1" with attribute oidcID: "33929"

  4. Scope vikunja_scope is not set:
    nothing happens

  5. oidcID is not set:
    You'll get error. Custom Scope malformed "The custom scope set by the OIDC provider is malformed. Please make sure the openid provider sets the data correctly for your scope. Check especially to have set an oidcID."

  6. In Vikunja I am in "team 3" with oidcID "", but the token does not deliver any data for "team 3":
    You will stay in team 3 since it was not set by the oidc provider

  7. In Vikunja I am in "team 3" with oidcID "12345", but the token does not deliver any data for "team 3":
    You will be signed out of all teams, which have an oidcID set and are not contained in the token. Especially if you've been the last team member, the team will be deleted.