Skip to content
Documentation

OIDC SSO: register an OpenID Connect application

This guide sets up OIDC SSO: it connects an application to SSOGEN with OpenID Connect (OIDC), using a client ID, exact redirect URIs and PKCE. It also covers service applications: programs that call an API as themselves, with no person present.

OpenID Connect is a standard way for an application to ask an identity system “who is this person?”. The application is called the client (or relying party). SSOGEN is the OpenID provider.

  1. The person opens the application. The application sends their browser to SSOGEN’s authorization endpoint, saying who it is (its client ID), where to send the person back (its redirect URI) and what it wants to know (its scopes).
  2. SSOGEN signs the person in, checks they may use the application, and sends the browser back to the redirect URI with a short-lived authorization code.
  3. The application’s server swaps the code for an ID token (a signed statement of who the person is) and an access token, at SSOGEN’s token endpoint.

SSOGEN applies two protections to every OpenID Connect sign-in, and your application’s library must support both:

  • PKCE with S256. The application proves it is the one that started the sign-in. SSOGEN accepts only the S256 method, and the authorization code flow only. There is no implicit or hybrid flow.
  • Exact redirect URIs. The address the application sends the person back to must match a registered address character for character: no wildcards, and a trailing slash makes it a different address. All registered addresses must start with https://.

Acme Wiki is a fictional web application that Acme Corp runs at https://wiki.acme.example. It uses a standard OpenID Connect library, which wants a client ID, an issuer (or discovery) address and a redirect address. SSOGEN is at https://sso.acme.example. Replace these with your own addresses.

  • You can sign in to the Admin Console with a role that may write applications.
  • A sign-in policy exists (see Choose how to connect an application).
  • You know the application’s redirect URI: the address on the application that receives the sign-in result. For Acme Wiki it is https://wiki.acme.example/auth/callback. Your application’s documentation names it (“redirect URI”, “callback URL”, “reply URL”).
  • You know whether the application can keep a secret (a server-side web app can; a single-page app or mobile app cannot). That decides Part C.

Part A: register the application in SSOGEN

Section titled “Part A: register the application in SSOGEN”
  1. In the Admin Console open Applications, then OpenID Connect. The list shows Name, Access mode, Status, Sign-in policy and Launch URL columns, and these buttons: App Packs, Register service and Register OpenID Connect app. Select Register OpenID Connect app.

    The OpenID Connect applications list

    With nothing registered yet, the list says “No OpenID Connect applications yet.” and “Register one to put it on people’s My Apps.”

  2. General. Fill in:

    Field Example
    Name Acme Wiki
    Slug acme-wiki (filled in from the name)
    Application ID app_acme_wiki (filled in from the name; it cannot be changed later)
    Category Collaboration
    My Apps icon (required) choose one from the library, or select Upload an image (PNG, JPEG, GIF or SVG up to 2 MB, resized automatically)

    Select Next.

  3. Access mode. Choose OpenID Connect: “App uses OpenID Connect.” Select Next.

  4. Connection. The step says: “Where the app is sent back after sign-in.” The Integration tab later gives you SSOGEN’s endpoints to put into the application. Fill in:

    Field Example Notes
    Redirect URI https://wiki.acme.example/auth/callback “One per line, https only. Exact match, no wildcards.” A trailing slash is a different URI. Up to 16, every one starting with https://. Add several lines if the application has several (for example a test and a live address).
    Client ID acme-wiki “Lowercase letters, digits, hyphens. A public client (PKCE, no secret) is created; add a client secret later under Sign-in tokens.” At most 64 characters, not a web address.
    The Connection step for an OpenID Connect application

    If the Client ID is empty you stay on the step and see “Enter a client ID, for example my-app-client.”; if there is no redirect address, “Enter at least one redirect URI, the address the application receives the sign-in at.”; an address that does not start with https:// or has a # part is refused with a message naming it.

    A Client ID can belong to one application only. If another application already uses it, saving fails (see the table at the end of this part).

  5. Sign-in policy. Choose the policy that decides how people sign in (see the SAML guide for the screen, which is the same).

  6. Identity sent to app. This step decides what the application learns about the person.

    • Identify the person by controls the sub claim, the person’s identifier in the ID token. With the default (shown as username — the SSOGEN account name (default)) the sub is SSOGEN’s stable internal identifier for the person, not a readable name. If you choose another attribute, such as email — mail address, the sub is that value instead. Choose the one the application matches its own accounts on. A person who has no value for the chosen attribute is refused rather than sent under a different kind of name.
    • Add an attribute adds an extra claim: type a name (for example department), choose where the value comes from (for example groups), and leave Required unticked unless the application cannot work without it (a person missing a required value is refused). Each extra attribute appears in the ID token as a claim of that name; a single value is a string, several values are a list.
    • The names iss, sub, aud, exp, iat, jti, auth_time, nonce, acr, amr, azp, at_hash, c_hash, s_hash, email, email_verified, name and preferred_username are reserved: SSOGEN will not send an extra attribute under one of them.

    The standard claims are released by scope: email with the email scope, and name and preferred_username with the profile scope. SSOGEN never sends email_verified, because it does not verify addresses itself.

  7. Launch. Type the address My Apps opens, which should start the application’s own sign-in: https://wiki.acme.example/login. Select Next. This saves the application (not yet active).

  8. Try it (optional). Pick Maria Lopez and select Run test. For an OpenID Connect application the result shows cards titled ID token claims and UserInfo claims, listing the claims the application would receive. Under Advanced, OpenID Connect scope lets you try a different scope list than openid profile email.

  9. Review and activate. Check the summary, which lists OAuth client ID and Redirect URIs, and select Activate application.

    The Review and activate step for an OpenID Connect application

If saving fails, the wizard stays on the step and says why:

Message What to do
That application ID is already in use. Choose a different one. Change Application ID.
The application ID may use only lowercase letters, numbers, underscores and hyphens. Fix Application ID.
Enter a name of 1 to 256 characters. Fix Name.
The slug may use only lowercase letters, numbers and hyphens. Fix Slug.
Application registration failed. Check the values on each step and try again. A general refusal. The usual causes for OpenID Connect: the Client ID is already used by another application, or it is not a valid client ID (use lowercase letters, numbers and single hyphens, up to 64 characters), or a redirect URI does not start with https://.

Part B: give SSOGEN’s values to the application

Section titled “Part B: give SSOGEN’s values to the application”
  1. Open Applications, OpenID Connect, then Acme Wiki, then the Integration tab. The section OpenID Connect (“For an application that speaks OpenID Connect to SSOGEN directly. Configure it with the discovery document if its library supports one.”) shows:

    Setting Value in this example Why
    Discovery document https://sso.acme.example/.well-known/openid-configuration Point the application’s library at this and the endpoints below are configured for it.
    Issuer https://sso.acme.example Must match the iss claim exactly.
    Client ID acme-wiki Also the aud of every token issued for this application.
    Redirect URI https://wiki.acme.example/auth/callback Matched exactly. No wildcards.
    Authorization endpoint https://sso.acme.example/oauth2/authorize Where the browser is sent.
    Token endpoint https://sso.acme.example/oauth2/token Where the code is redeemed.
    JWKS https://sso.acme.example/oauth2/jwks The keys that verify what SSOGEN signs.
    UserInfo endpoint https://sso.acme.example/oauth2/userinfo Claims for an authenticated person.
    End session endpoint https://sso.acme.example/oauth2/logout Signs the person out of SSOGEN, not only out of the application.
    PKCE Required, S256 The only method accepted.
    Response type code Authorization code only.
    Scopes openid profile email What SSOGEN will honour.
    The Integration tab of the OpenID Connect application

    A line above the tables says: “Every grant and token issued for this application carries aud: acme-wiki — the same value as the client ID, because that is what an audience is.”

    Your values use your own SSOGEN address.

  2. Configure Acme Wiki. This happens in the application, not in SSOGEN. Most libraries need only the issuer (or discovery address), the client ID, the redirect address and the scopes. For example, a configuration file for the application:

    OIDC_DISCOVERY_URL=https://sso.acme.example/.well-known/openid-configuration
    OIDC_CLIENT_ID=acme-wiki
    OIDC_REDIRECT_URI=https://wiki.acme.example/auth/callback
    OIDC_SCOPES=openid profile email
    OIDC_USE_PKCE=true
    OIDC_PKCE_METHOD=S256

    The names on the left are invented for this example; use your application’s own setting names. If the application is built on the Python library Authlib, the equivalent is:

    oauth.register(
    name="ssogen",
    client_id="acme-wiki",
    server_metadata_url="https://sso.acme.example/.well-known/openid-configuration",
    client_kwargs={
    "scope": "openid profile email",
    "code_challenge_method": "S256",
    "token_endpoint_auth_method": "none",
    },
    )

    token_endpoint_auth_method: none means “public client, PKCE only”, which is how a new OpenID Connect application is registered. For a client with a secret see Part C.

  3. Check that the application can reach SSOGEN, from the application’s server:

    curl -s https://sso.acme.example/.well-known/openid-configuration

    You should see a JSON document that includes "issuer", "authorization_endpoint", "token_endpoint", "jwks_uri", "code_challenge_methods_supported":["S256"] and "scopes_supported":["openid","profile","email"]. If the command fails, the application’s server cannot reach SSOGEN over HTTPS: check DNS, the firewall and that the server trusts SSOGEN’s certificate.

Part C: keep a secret, or use a key (Sign-in tokens tab)

Section titled “Part C: keep a secret, or use a key (Sign-in tokens tab)”

A new OpenID Connect application is registered as a public client: it proves itself with PKCE only and has no secret. That is right for single-page and mobile apps. A web application with a server can do better, by also proving who it is when it redeems the code.

Open the application, then the Sign-in tokens tab. (This tab appears for applications that have an OpenID Connect registration.) It has two sections.

The Sign-in tokens tab with the Client secret option chosen

“How the application proves its identity when requesting tokens.” Choose one:

Choice Meaning Use it for
None — PKCE only “For public clients (SPA, mobile). Not for service applications.” Single-page and mobile apps.
Private key JWT (recommended) “Register public keys only (RS256 2048+ or ES256).” The application signs a short assertion with its private key; SSOGEN keeps no shared secret. Server applications that can keep a private key.
Client secret “Generated secret, shown once. SSOGEN stores only a hash.” Server applications that cannot use keys.

To use a client secret:

  1. Select Client secret. The page says “No client secret is issued yet.”

  2. Under When replacing a secret leave the default for the first secret (it only matters later).

  3. Select Generate secret. A warning appears: “You will not see this again. Copy the secret now and store it in your secret manager.” The secret is shown in the field Client secret with a copy control. Copy it into your secret store and select I have copied it.

    The client secret is shown once
  4. Select Save sign-in settings. The page says “Saved. As it is now: client authentication client secret; refresh tokens off.” (The secret must be issued first; if you save the method before generating one the console shows the refusal code client_secret_required.)

  5. In the application, set its client secret and use client secret basic (HTTP Basic authentication at the token endpoint) as the token endpoint authentication method.

    client_kwargs={"scope": "openid profile email",
    "code_challenge_method": "S256",
    "token_endpoint_auth_method": "client_secret_basic"}

    and supply client_secret from your secret store, never from source code.

To replace a secret later: return to the tab, choose the old-secret behavior under When replacing a secret (Retire the old secret at once, Keep the old secret for 1 hour, Keep the old secret for 24 hours (default) or Keep the old secret for 7 days), and select Generate secret again. While the old one is kept, the tab says “The previous secret keeps working until” and the time. Use Retire the old secret at once if the secret leaked.

To use a private key: select Private key JWT (recommended), paste the Public keys (JWKS) (public keys only, at most 5, each with a unique kid; see “Make a key pair” below), and select Save sign-in settings. Refusals name the key at fault, for example “Key 1 (acme-wiki-1) contains private key material. Paste public keys only.”

“Lets the app renew access without a new sign-in. Authorization code flow only.” Turn on the switch Refresh tokens and choose:

Setting Choices Default
Ends after no use for 5 minutes, 15 minutes, 30 minutes, 1 hour, 8 hours, 24 hours 30 minutes
Ends at the latest after (never longer than the person’s sign-in session) 1 hour, 8 hours, 24 hours, 7 days, 30 days 8 hours

Select Save sign-in settings. Access tokens for a person’s sign-in are short-lived (five minutes by default); a refresh token lets the application get new ones until the limits above. Turning refresh tokens off stops further refreshes; the page notes that it does not revoke sign-ins that already exist.

  1. Open the Assignments tab and assign Maria Lopez or a group (see Assign people, set the sign-in policy, test and activate).
  2. As Maria Lopez, open My Apps and select the tile Acme Wiki. She goes to https://wiki.acme.example/login, which sends her to SSOGEN, and then back to the redirect address signed in.
  3. In the application, you should see her as maria.lopez or her email, according to Identify the person by.
What you see Cause and fix
An SSOGEN page “Sign-in refused”: “The application sent a sign-in request this service cannot accept. Go back to the application and try again.” The request was malformed or does not match the registration. Check: the Client ID is exactly the registered one; the redirect URI matches exactly (scheme, host, port, path, trailing slash); the library sends code_challenge with code_challenge_method=S256; the application asks for the code response type.
The browser comes back to the redirect URI with error=access_denied SSOGEN refused the person: not assigned, the application is not active, or the sign-in policy denies them. Run Test on the application as that person; it names the failed check.
The application’s server cannot redeem the code, with an invalid_client or similar answer The token endpoint authentication method in the application does not match Client authentication in the Sign-in tokens tab (for example it sends a secret to a public client).
The application says the token’s issuer or audience is wrong Use the Issuer from the Integration tab, and expect aud to equal the client ID.
The person arrives with no email or name The email or profile scope was not requested, or the person has no value in SSOGEN.
The sub is a long identifier, not a name Identify the person by is at its default. Choose another attribute in Identity sent to app and Save.

The Integration tab lists an End session endpoint, https://sso.acme.example/oauth2/logout, which signs the person out of SSOGEN, not only out of the application.

To let the browser come back to your application afterward, open the Sign-in tokens tab (it shows Sign-out addresses for browser-based OpenID Connect applications) and enter the addresses in Post-logout redirect URIs, one https:// address per line, at most 16: “Where the browser may go after sign-out. Exact-match https URLs.”

  • A request to the end session endpoint with a post_logout_redirect_uri that is registered there ends the person’s SSOGEN session and sends the browser to that address. If the person had signed in through another identity provider that also ends its session, the browser goes there first and then on to your address.
  • A request with a post_logout_redirect_uri that is not registered is refused with an HTTP 400 error.
  • A request without it ends the session and answers with an empty successful response (HTTP 204), so the browser shows nothing new.

Have your application end its own session first, then call the end session endpoint. Turning off Refresh tokens does not revoke sign-ins that already exist; use a shorter lifetime if that matters to you.

A service application is a program that calls an API as itself: a nightly report job, a build system, an integration. There is no person, no redirect and no My Apps tile. It gets an access token about itself, and the API it calls checks the token.

  1. Open Applications, OpenID Connect, then Register service. The wizard is titled Register service application, with a breadcrumb Applications and four steps: Details, Access, Authentication, Review.

  2. Details.

    Field Example Notes
    Name Acme Nightly Report
    Slug acme-nightly-report
    Application ID app_acme_nightly_report
    Client ID acme-nightly-report “Becomes the subject of the tokens it is issued. It is retired for good if the application is deleted.” Lowercase letters, numbers and single hyphens; it cannot look like a UUID.
  3. Access.

    Field Example Notes
    Audiences (required) https://reports.acme.example/api “APIs it may request tokens for: https URIs or urn: names, one per line.” The API you will call. If you leave it empty: “Name at least one audience: the API this application will call.”
    Scopes reports.read Optional, one per line. The permissions you define for your API. “Optional, one per line. openid, profile and email are for people and are refused.”
    Access token lifetime 15 minutes (default) Choices: 5 minutes, 15 minutes (default), 30 minutes, 1 hour.
    The Access step of the service application wizard
  4. Authentication. Under Method choose:

    • Private key JWT (you supply the public keys), then paste the Public keys (JWKS) (“Public keys only, at most 5, each with a unique kid.”), or
    • Client secret (generated for you): “A client secret is generated and shown once.”
  5. Review. Shows Name, Client ID, Token endpoint and Client authentication, and a sample request: “Once registered, the application asks for a token like this:”. The note says “A service application has no redirect URI and no assignments: it signs in as itself.” Select Register.

    The Review step of the service application wizard
  6. If you chose Client secret, a page titled “Acme Nightly Report is registered” shows the secret once, with the warning “You will not see this again. Copy the secret now and store it in your secret manager.” Select I have copied it. With a private key you go straight to the application.

A service application is active as soon as it is registered. Some scopes let a service application act on SSOGEN’s own automation interface; registering such a scope is limited to super administrators.

If registration is refused, the message names the cause, for example “The keys were refused (key 1): …”.

For Private key JWT you create a key pair and give SSOGEN only the public half. This example uses Python with the jwcrypto package (pip install jwcrypto):

from jwcrypto import jwk
key = jwk.JWK.generate(kty="EC", crv="P-256", kid="acme-nightly-report-1")
open("acme-nightly-report.private.json", "w").write(key.export_private())
print('{"keys": [' + key.export_public() + ']}')

Paste the printed text into Public keys (JWKS). Keep the .private.json file in your secret store; it never goes to SSOGEN. SSOGEN accepts RSA keys (for RS256, 2048-bit or larger) and EC P-256 keys (for ES256). Keys containing private values are refused: “contains private key material. Paste public keys only.”

The application sends a request to the token endpoint, https://sso.acme.example/oauth2/token.

With a client secret:

curl -X POST https://sso.acme.example/oauth2/token \
-u acme-nightly-report:"$CLIENT_SECRET" \
-d grant_type=client_credentials \
--data-urlencode resource=https://reports.acme.example/api

With a private key. The application signs a short-lived assertion with its private key and sends it:

import time, uuid
from jwcrypto import jwk, jwt
key = jwk.JWK.from_json(open("acme-nightly-report.private.json").read())
now = int(time.time())
claims = {
"iss": "acme-nightly-report",
"sub": "acme-nightly-report",
"aud": "https://sso.acme.example/oauth2/token",
"iat": now,
"exp": now + 120,
"jti": str(uuid.uuid4()),
}
token = jwt.JWT(header={"alg": "ES256", "kid": "acme-nightly-report-1"}, claims=claims)
token.make_signed_token(key)
print(token.serialize())
curl -X POST https://sso.acme.example/oauth2/token \
-d grant_type=client_credentials \
-d client_id=acme-nightly-report \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
-d client_assertion="$ASSERTION" \
--data-urlencode resource=https://reports.acme.example/api

The rules for the assertion, all checked by SSOGEN: it is signed with RS256 or ES256 (never none or a shared-secret algorithm); iss and sub both equal the client ID; aud is a single value equal to SSOGEN’s issuer (https://sso.acme.example) or its token endpoint address; exp is present and no more than 300 seconds after iat; and jti is unique, because each assertion can be used once. Allow about a minute of clock difference between the machines. The kid in the header must be one you registered (it may be left out if exactly one key is registered).

The answer is a JSON document with access_token, token_type (Bearer), expires_in (seconds) and scope. There is never a refresh token: ask for a new access token when it expires.

Request detail Rule
resource The audience the token is for. It must be one of the registered Audiences. Leave it out only if exactly one audience is registered. Only one resource is allowed. A wrong one gives invalid_target.
scope Optional, space-separated. Each must be a registered scope. Leave it out to get every registered scope. An unregistered one, or openid, profile or email, gives invalid_scope.

The API that receives the token (a “resource server”) should:

  1. Fetch SSOGEN’s signing keys from the JWKS address (https://sso.acme.example/oauth2/jwks) and verify the token’s signature.
  2. Check iss is https://sso.acme.example, aud is the API’s own identifier (https://reports.acme.example/api), and exp has not passed.
  3. Check the token’s scope covers what the call needs.

Keep the Access token lifetime short. If the service application is disabled afterwards, SSOGEN treats its tokens as inactive when asked (UserInfo and introspection), but an API that checks the token locally goes on accepting it until it expires.

  • Dynamic client registration (SSOGEN does not offer it).
  • Per-client access token lifetime for people’s sign-ins (fixed at five minutes).