Sign in with Monotree

Partner apps can sign Monotree users in with standard OpenID Connect: the authorization code flow with PKCE. A user can also open your app from the Monotree app and arrive signed in, with no login prompt. This page is for the partner developer.

Overview

Monotree is an OpenID Connect provider. Your app or site is a relying party. A user reaches you in one of two ways, and both end in the same authorization code flow, so you build it once.

The user startsWhat happens
In the Monotree app, by tapping a link to your appMonotree opens your login initiation URI with a one-time login_hint. You start the normal flow and pass the hint along. The user is signed in with no prompt. See Flow 1.
In your app or on your site, by tapping "Sign in with Monotree"You start the normal flow. The user signs in to Monotree. See Flow 2.

Every Monotree customer runs on its own server and is its own issuer, for example https://acme.monotree.com. There is no shared Monotree login host. You already know which customer a user belongs to, so you know which issuer to use.

Only the authorization code flow is supported, and Monotree issues no refresh tokens. Keep your own session after sign-in. For data about the user beyond who they are, use the Open API.

Discovery and endpoints

Fetch the discovery document per customer and read the endpoints from it. Do not hard-code them.

GET https://acme.monotree.com/.well-known/openid-configuration
EndpointPath on the issuerPurpose
Authorization/oauth/authorizeStart of every sign-in
Token/oauth/tokenExchange the code for tokens
UserInfo/oauth/userinfoThe same claims as the id_token, with the access token
JWKS/oauth/jwksPublic keys to verify id_token signatures

The discovery document also lists:

  • scopes_supported: openid, profile, email
  • response_types_supported: code
  • code_challenge_methods_supported: S256
  • id_token_signing_alg_values_supported: RS256
  • token_endpoint_auth_methods_supported: none (native apps), client_secret_basic and client_secret_post (web backends). We recommend client_secret_basic.

For an example, see the demo server's document: demo.monotree.com/.well-known/openid-configuration.

Discovery, JWKS and UserInfo answer 404 on a Monotree server until a partner client is registered there.

Registering a client

Monotree registers clients per Monotree customer. There is no dynamic registration. Send us the following for each customer you connect:

You sendExampleNotes
Client typepublicpublic for a native app (PKCE, no secret). confidential for a web backend (client secret)
NameYour appYour app's name in Monotree's records
Redirect URIshttps://app.partner.example/auth/monotree/callbackExact match, no wildcards. Native apps can use a universal link / App Link, or a private-use scheme such as com.partner.app:/auth/monotree. Plain http only on localhost
Login initiation URIhttps://app.partner.example/auth/monotree/initiateWhere launches from the Monotree app start (Flow 1)
Target link prefixhttps://app.partner.example/Every target_link_uri must start with it. Must end with /

You get back a client_id, and a client_secret for a confidential client. Each customer is its own issuer, so you get a different client_id on each Monotree server.

Flow 1: launch from the Monotree app

This is OpenID Connect third-party initiated login, the same pattern as LTI 1.3. The user is already signed in to the Monotree app. Monotree gives you a one-time login_hint that signs them in to exactly one authorization request.

  1. The user taps a link to your app in the Monotree app.
  2. Monotree opens your login initiation URI with iss, login_hint and target_link_uri.
  3. You start a normal authorization request to the issuer, with the login_hint unchanged.
  4. Monotree redirects straight back to your redirect_uri with a code. No screen is shown.
  5. You exchange the code for tokens, sign the user in, and open target_link_uri.

Monotree opens your login initiation URI (GET):

https://app.partner.example/auth/monotree/initiate
  ?iss=https%3A%2F%2Facme.monotree.com
  &login_hint=lt_9f3c2a7e5d1b4c08a6e2
  &target_link_uri=https%3A%2F%2Fapp.partner.example%2Fitems%2F42
  &client_id=7
  • iss: which Monotree customer. Reject issuers you are not registered with.
  • login_hint: opaque, one-time, valid for 60 seconds. Pass it on unchanged. Never store or log it.
  • target_link_uri: where to take the user after sign-in. Check that it starts with your target link prefix.

You start a normal authorization request, adding login_hint:

GET https://acme.monotree.com/oauth/authorize
  ?response_type=code
  &client_id=7
  &redirect_uri=https%3A%2F%2Fapp.partner.example%2Fauth%2Fmonotree%2Fcallback
  &scope=openid%20profile%20email
  &state=af0ifjsldkj
  &nonce=n-0S6_WzA2Mj
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &login_hint=lt_9f3c2a7e5d1b4c08a6e2
  • Never send prompt=login on a launch. It asks the user to sign in again, which defeats the launch.
  • A native app opens this in the system browser sheet (ASWebAuthenticationSession on iOS, Custom Tabs on Android). Use an ephemeral session for launches (prefersEphemeralWebBrowserSession on iOS).
  • A launch signs the user in to this one request only. It does not leave a Monotree session in the browser.
  • If the hint has expired or was already used, the user sees the Monotree login page instead, as in Flow 2, and can still sign in.

Links in the Monotree app: a CMS menu link of type External app launches through Flow 1 automatically when its URL starts with your target link prefix.

Flow 2: sign in started in your app

The user taps "Sign in with Monotree" in your app or on your site. It is the same authorization request as Flow 1, without login_hint.

  1. You send the user to the issuer's authorization endpoint.
  2. The user sees the Monotree login page and signs in, with their Monotree password or through their organisation's identity provider if one is set up. If they already have a Monotree session in that browser, they skip this step.
  3. Monotree redirects back to your redirect_uri with a code.
  4. You exchange the code for tokens.

Some customers require two-factor login. There, an employee with no phone number on their Monotree profile cannot complete a sign-in started in your app. Launches from the Monotree app (Flow 1) always work, and so does signing in through the organisation's identity provider.

There is no consent screen: the customer approved your app when Monotree registered it.

prompt and max_age are supported. With prompt=login, or when the user's sign-in is older than max_age seconds, the user signs in again.

Exchanging the code

Token request from a native app (public client):

POST https://acme.monotree.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fapp.partner.example%2Fauth%2Fmonotree%2Fcallback
&client_id=7
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

A confidential client authenticates with HTTP Basic (client_id:client_secret) instead of sending client_id in the body. Codes are single-use and expire after 60 seconds. If a used code is sent again, the access token it gave is revoked.

The token endpoint does not allow cross-origin browser calls. Call it from your backend or from the native app, not from JavaScript in a web page.

Response:

{
  "token_type": "Bearer",
  "access_token": "eyJ0eXAiOiJKV1Qi...",
  "expires_in": 600,
  "id_token": "eyJraWQiOiIyMDI2LTA5IiwiYWxnIjoiUlMyNTYifQ..."
}

PKCE

  • Public clients (native apps): PKCE with S256 is required.
  • Confidential clients: PKCE with S256 is recommended.
  • plain is never accepted.

Tokens, scopes and claims

The id_token says who the user is, and it is small on purpose. For more (department, groups, manager), call the Open API with a token the customer issues you, using sub as the user ID.

ClaimScopeValue
issopenidThe customer's issuer, for example https://acme.monotree.com
subopenidThe Monotree user ID as a string, for example "512". The same ID the Open API uses
audopenidYour client_id
exp, iatopenidExpiry and issue time
nonceopenidThe nonce from your authorization request
auth_timeopenidWhen the user signed in to Monotree, or the moment of the launch
name, given_name, family_nameprofileThe user's name as shown in Monotree
pictureprofileAvatar URL, when the user has one
emailemailThe user's current email
  • sub is unique per issuer. Two customers can have a user with the same sub, so key users on iss + sub.
  • Do not key users on email: it can change. There is no email_verified claim.

Example id_token payload:

{
  "iss": "https://acme.monotree.com",
  "sub": "512",
  "aud": "7",
  "exp": 1790255100,
  "iat": 1790254800,
  "auth_time": 1790254795,
  "nonce": "n-0S6_WzA2Mj",
  "name": "Jane Doe",
  "given_name": "Jane",
  "family_name": "Doe",
  "email": "jane@example.com"
}
TokenLifetime
Launch login_hint60 seconds, one use
Authorization code60 seconds, one use
id_token5 minutes
Access token (for UserInfo only)10 minutes
Refresh tokenNot issued

The access token is only for /oauth/userinfo. It does not work on the Open API.

Signing keys

id_tokens are signed with RS256. Each Monotree server has its own key pair, published in its JWKS with a kid.

  • Pick the key by the kid in the token header. Never pin a single key.
  • Cache the JWKS, and on an unknown kid fetch it again once before you reject the token.
  • Keys rotate yearly, and at once after a suspected compromise. A new key appears in the JWKS 7 days before Monotree starts signing with it, and the old key stays 7 days after.

The JWKS URL is the only source of keys. Monotree never sends keys by email.

What Monotree does not support

  • Refresh tokens and offline access.
  • Request objects: an authorization request with request or request_uri is refused.
  • Dynamic client registration for partners.
  • Single logout. Signing out of Monotree does not sign the user out of your app.
  • Groups, department or role claims in the id_token. Use the Open API.

Partner checklist

Check every id_token:

  • The signature against the issuer's JWKS, by kid. Algorithm RS256 only.
  • iss is exactly the issuer you started the flow with.
  • aud contains your client_id.
  • exp is in the future (allow at most 60 seconds of clock skew).
  • nonce is the one you sent.

In every flow:

  • PKCE with S256, with a new code_verifier for each attempt.
  • A new state, checked when the user comes back.
  • Identify the user by iss + sub, never by email.
  • Pass login_hint on unchanged. Do not store or log it.
  • Only follow a target_link_uri that starts with your target link prefix.
  • Never send prompt=login on a launch.

Native apps (RFC 8252):

  • Open the authorization request in the system browser sheet, never in an embedded WebView. Use an ephemeral session for launches.
  • Use a universal link / App Link for the login initiation URI, so a launch from Monotree opens your app.
  • Register as a public client. Do not ship a client secret in the app.