Add a "Sign in with MusiMap" button to your product and let users authenticate with their existing MusiMap account — no new password to remember, no new profile to manage. It works the same way as "Sign in with Google" or "Sign in with Apple": your app hands off the login UI to us, and we hand back a verified user identity.

MusiMap is a standard OpenID Connect (OIDC) provider on top of OAuth 2.0 Authorization Code flow with PKCE. If your framework already has an OIDC client, point it at our discovery document and you're done.

Just calling the API from your server?

Use the client-credentials flow instead. This page is for letting end users sign in.

Overview

The round-trip looks like this:

  1. User clicks Sign in with MusiMap in your app.
  2. You redirect them to our authorize URL with your client_id, a redirect_uri, a state, a nonce and a PKCE code_challenge.
  3. We authenticate the user (password or Google SSO, per your client's policy) and show them a consent screen listing the scopes you asked for.
  4. We redirect back to your redirect_uri with a one-time code and the same state.
  5. Your server exchanges the code (plus the PKCE code_verifier) for an access_token, an id_token, and a refresh_token.
  6. You verify the id_token, create a local session for the user, and you're done.

All the moving parts — authorize, token, userinfo, revoke, JWKS — are advertised via our OIDC discovery document.

Prerequisites

Before you start
  1. A MusiMap OAuth client of type public or confidential (we'll tell you which in the dashboard).
  2. At least one registered redirect URI. http://localhost is allowed for local dev; production must be https://.
  3. The ability to run the code_verifier/code_challenge PKCE exchange on a server (for confidential clients) or in a backend-for-frontend (for SPAs).
  4. A session store on your side (cookies, Redis, a database — anything) to hold state, nonce and the PKCE verifier between the two hops.

Step 1 — Build the authorize URL

When the user clicks your sign-in button, redirect their browser to https://accounts.musimap.com/oauth/authorize with the parameters below. Generate a fresh state, nonce and PKCE pair for every request, and persist them server-side keyed by the user's session:

import base64, hashlib, os, secrets
from urllib.parse import urlencode

def build_authorize_url(client_id: str, redirect_uri: str):
    # PKCE — generate a verifier + S256 challenge.
    verifier = secrets.token_urlsafe(64)
    challenge = base64.urlsafe_b64encode(
        hashlib.sha256(verifier.encode()).digest()
    ).rstrip(b'=').decode()

    # Random state + nonce (CSRF + replay protection).
    state = secrets.token_urlsafe(32)
    nonce = secrets.token_urlsafe(32)

    # You MUST persist (verifier, state, nonce) server-side
    # to validate them when the user comes back.

    params = {
        'response_type':         'code',
        'client_id':             client_id,
        'redirect_uri':          redirect_uri,
        'scope':                 'openid profile email',
        'state':                 state,
        'nonce':                 nonce,
        'code_challenge':        challenge,
        'code_challenge_method': 'S256',
    }
    return (
        'https://accounts.musimap.com/oauth/authorize?'
        + urlencode(params),
        verifier,
        state,
        nonce,
    )
Don't skip PKCE.

PKCE (S256) is required for all clients — public and confidential. It costs you two lines of code and it's the single best defence against code-injection attacks in OAuth. If you omit code_challenge, the authorize request will be rejected.

Step 2 — User authenticates and consents

We take over from here. The user sees the MusiMap login page (password or Sign in with Google, depending on how your OAuth client is configured), then — on first use or when scopes change — a consent screen listing exactly what your app is asking for.

If the user denies, we redirect back to your redirect_uri with ?error=access_denied&error_description=...&state=.... Show a friendly "OK, no problem" screen; don't silently retry.

If the user allows, we redirect back with ?code=...&state=.... The code is a one-time value that expires in 60 seconds.

Step 3 — Exchange the code for tokens

On your redirect_uri handler, first verify that the state matches what you stored. Then POST the code plus the PKCE code_verifier you kept around to our token endpoint:

curl -X POST https://api.musimap.com/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code' \
  -d 'code=THE_CODE_FROM_THE_CALLBACK' \
  -d 'redirect_uri=https://your-app.example.com/oauth/callback' \
  -d 'client_id=YOUR_CLIENT_ID' \
  -d 'client_secret=YOUR_CLIENT_SECRET' \
  -d 'code_verifier=THE_VERIFIER_YOU_STORED'

The response contains everything you need to log the user in:

200 OK
{
  "access_token":       "eyJhbGciOiJSUzI1NiIs...",
  "id_token":           "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token":      "rt_01HXYZ...",
  "token_type":         "Bearer",
  "expires_in":         3600,
  "refresh_expires_in": 2592000,
  "scope":              "openid profile email"
}

Before trusting the id_token, validate it against the signing keys at our JWKS endpoint. Every mainstream OIDC library does this for you — check the iss, aud, exp and nonce claims.

Step 4 — Read the user's profile

The id_token already contains the standard claims for the scopes you requested. If you'd rather fetch them fresh (e.g. after a refresh, or to get the latest avatar), hit the /oauth/userinfo endpoint with the access token:

curl https://api.musimap.com/oauth/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"
200 OK
{
  "sub":            "user_01HXYZABCD...",
  "name":           "Alex Morgan",
  "given_name":     "Alex",
  "family_name":    "Morgan",
  "picture":        "https://cdn.musimap.com/avatars/alex.jpg",
  "locale":         "en",
  "email":          "alex@example.com",
  "email_verified": true
}

The fields returned depend on the scopes actually granted — which may be a subset of what you asked for if the user unchecked something in the consent screen.

Scopes

Request only what you need. Users are much more likely to say "yes" to a narrow scope list than to a broad one, and our consent screen shows them exactly what you're asking for.

Scope Required Grants access to
openid Yes Required for OpenID Connect. Returns the user's stable MusiMap ID (sub claim) and an ID token.
profile No User's display name, given/family name, picture and locale.
email No User's email address and a boolean telling you whether it's been verified.

ID token claims

Here are the standard OIDC claims you'll see in the id_token and /userinfo response, grouped by the scope that unlocks them:

Claim Scope Example Description
iss openid https://accounts.musimap.com The issuer — always this exact string. Always verify it.
sub openid user_01HXYZABCD... Stable MusiMap user ID. Never changes. Use this as your primary key.
aud openid your_client_id The client ID you passed in the authorize request. Verify this matches yours.
exp openid 1745170820 ID token expiry, epoch seconds. Tokens are short-lived (~10 min).
iat openid 1745170220 Issued-at, epoch seconds.
nonce openid a3f1c... The nonce you sent in the authorize request. Verify it matches.
name profile Alex Morgan Preferred display name, as set in their MusiMap profile.
given_name profile Alex First name.
family_name profile Morgan Last name.
picture profile https://cdn.musimap.com/avatars/... Avatar URL. May be null if the user hasn't set one.
locale profile en BCP-47 language tag. One of en, fr, es today.
email email alex@example.com User's email address.
email_verified email true Whether we've verified the address. Don't trust unverified emails for auth decisions.

Calling the Web API after sign-in

Once you have an access token from the authorisation code flow, you can call versioned Web API endpoints at https://api.musimap.com/v1/* with Authorization: Bearer <token>. Use GET /v1/me to confirm the user's organisation and catalog context before making business calls.

For OpenID Connect user claims (email, name, and so on), use GET /oauth/userinfo instead. That endpoint returns OIDC claims directly and is not wrapped in the MusiMap envelope. See Authentication for the difference between /oauth/* and /v1/*.

Refreshing the access token

Access tokens live for one hour. Refresh tokens live longer (currently 30 days — see refresh_expires_in in the response). When the access token expires, trade the refresh token in for a new pair:

curl -X POST https://api.musimap.com/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=refresh_token' \
  -d 'refresh_token=THE_REFRESH_TOKEN' \
  -d 'client_id=YOUR_CLIENT_ID' \
  -d 'client_secret=YOUR_CLIENT_SECRET'

If the refresh call fails with invalid_grant, the refresh token has been revoked or has expired — you must send the user back through the full login flow. Clear your local session and show the sign-in button again.

Logging the user out

Logging the user out of your app is your business (drop the cookie, clear the session). To also invalidate the tokens on our side, call /oauth/revoke. We recommend doing this on logout, especially for shared devices:

curl -X POST https://api.musimap.com/oauth/revoke \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'token=THE_ACCESS_OR_REFRESH_TOKEN' \
  -d 'token_type_hint=refresh_token' \
  -d 'client_id=YOUR_CLIENT_ID' \
  -d 'client_secret=YOUR_CLIENT_SECRET'

Revoking a refresh token invalidates all access tokens derived from it. You can use token_type_hint of either access_token or refresh_token; if you're unsure, omit the hint — we'll figure it out.

Discovery & JWKS

Most OIDC libraries only need one thing: the URL of the discovery document. Ours lives here and lists every endpoint, every supported algorithm, every scope:

https://accounts.musimap.com/.well-known/openid-configuration
{
  "issuer":                                "https://accounts.musimap.com",
  "authorization_endpoint":                "https://accounts.musimap.com/oauth/authorize",
  "token_endpoint":                        "https://api.musimap.com/oauth/token",
  "userinfo_endpoint":                     "https://api.musimap.com/oauth/userinfo",
  "revocation_endpoint":                   "https://api.musimap.com/oauth/revoke",
  "jwks_uri":                              "https://accounts.musimap.com/.well-known/jwks.json",
  "response_types_supported":              ["code"],
  "grant_types_supported":                 ["authorization_code", "refresh_token"],
  "subject_types_supported":               ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "scopes_supported":                      ["openid", "profile", "email"],
  "code_challenge_methods_supported":      ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"]
}

The public signing keys used to sign id_tokens are published at https://accounts.musimap.com/.well-known/jwks.json and rotate periodically — don't pin a specific kid in your code; always fetch the JWKS dynamically and cache it for a few hours.

Security checklist

  1. Always use PKCE (S256). It's required.
  2. Always generate and verify state. Tie it to the user's pre-login session to prevent CSRF on the callback.
  3. Always generate and verify nonce in the id_token. It prevents token replay.
  4. Never put client_secret in a browser. SPAs and native apps should use a thin backend-for-frontend that holds the secret.
  5. Store session tokens in httpOnly cookies where possible — not localStorage. Use Secure + SameSite=Lax at minimum.
  6. Validate iss and aud on every ID token. A library will do this; make sure it's actually switched on.
  7. Don't trust email without email_verified: true. Use sub as your primary key.

Button assets

Branded "Sign in with MusiMap" buttons (light, dark, monochrome) will ship with the launch of the developers portal. In the meantime, use a neutral button with the text "Sign in with MusiMap" and our wordmark. We'll publish updated brand guidelines and downloadable SVGs before GA.

Where to go next