Skip to Content
For DevelopersSign in with UniClubs

Sign in with UniClubs

UniClubs is an OpenID Connect provider. Your site sends students to UniClubs to log in, they approve sharing once, and you get back who they are and (if you ask) whether they’re a member of your club.

Register your app

Under Settings → API & OAuth → OAuth applications, a club admin creates an application with:

  • Redirect URIs. As many as you need, including http://localhost:3000/... for local development. They must match exactly (scheme, host, port, path).
  • Scopes your app may request (see below).

You get a client_id (unc_…) and a client_secret. The secret is shown once: store it straight away.

Redirect URIs and scopes can’t be changed after creation. To change them, create a new application and switch your site to the new credentials, then delete the old one. Add your localhost URIs up front.

Endpoints

Most OIDC libraries only need the discovery URL:

https://uniclubs.ch/.well-known/openid-configuration
Issuerhttps://uniclubs.ch
Authorizehttps://uniclubs.ch/api/oauth/authorize
Tokenhttps://uniclubs.ch/api/oauth/token
Userinfohttps://uniclubs.ch/api/oauth/userinfo
JWKShttps://uniclubs.ch/api/oauth/.well-known/jwks.json
Revoke / introspect/api/oauth/revoke, /api/oauth/introspect

The flow

Authorization-code flow, with response_type=code.

  • The code exchange needs your client secret (HTTP Basic or client_secret_post). Do it on your server, never in browser JavaScript. There are no public (secret-less) clients, so a pure single-page app needs a small backend.
  • PKCE is supported (S256 only) and recommended on top of the secret.
  • Pass a nonce and it’s echoed in the ID token.
  • The ID token is an RS256 JWT, verifiable against the JWKS.

Lifetimes: authorization code 10 min (single use), access token 1 h, ID token 1 h. Request offline_access to get a refresh token (30 days). Refresh tokens rotate on every use: always store the new one. Replaying an old refresh token revokes the whole chain.

Scopes and what you get back

Claims come from the userinfo endpoint. The ID token carries sub, name, email, and picture.

ScopeClaims
openidsub, plus previous_ids when there are older IDs to re-link
profilename, given_name, family_name, picture, profile, gender, locale, updated_at
emailemail, email_verified, email_uni, email_private
user:readbio, linkedin_url, graduation_year, major, phone, has_cv, current_cv_id, current_cv_url
membership:readmemberships[], club_role (see below)
offline_accessa refresh token
files:writelets your app upload a CV to the student’s profile (details in the spec )

About the email claims: email is the student’s verified private address if they have one, otherwise their university address. email_uni is always the university address and is always verified. email_verified refers to the private address only.

Is this person a member?

Request membership:read. Userinfo then includes the student’s active memberships in your club:

{ "sub": "ucs_3fZq9xT2bKpL0aVw7eRn1Q", "memberships": [ { "id": "…", "club_slug": "consulting-club", "status": "active", "tier": "Full Member", "start_date": "2026-09-01T00:00:00.000Z", "end_date": "2027-08-31T23:59:59.000Z" } ], "club_role": null }
  • Not a member, or the membership has expired: the memberships and club_role fields are left out entirely. Treat a missing field as “not a member”.
  • end_date is when the membership ends. It’s null for lifetime memberships.
  • club_role is the student’s staff role in your club (admin, recruiter, viewer) or null.

This only answers the question at the moment of sign-in. To check later without the student present (e.g. a nightly sync), use the REST API with the stored sub: GET /api/external/v1/members/{sub} and look for a membership with status: "active".

Keeping IDs stable

sub is your club’s permanent ID for the student. Use it as your key, not the email address, since students can change their emails.

When a student merges two duplicate UniClubs accounts, you keep seeing the ID you already stored. The only exception is if your club knew both accounts: you then get one of the two IDs as sub, and the other in previous_ids:

{ "sub": "ucs_3fZq…", "previous_ids": ["ucs_Hk81…"] }

Handle previous_ids at sign-in: if you don’t recognise sub, look for a local user stored under one of the previous_ids and switch them to the new sub. The claim is in both userinfo and the ID token, and is left out when empty. Old IDs also keep working in the REST API, which always answers with the current ID.

Switch to club IDs on 11 November 2026. Clubs that integrated before that date currently receive global UniClubs IDs. On 11 November every club switches to club-scoped IDs: sub becomes a ucs_… ID and the old global ID shows up in previous_ids. If you handle previous_ids as described above, your users are re-linked automatically on their next sign-in. Tokens from an existing sign-in (including refreshed ones) keep the ID they started with until then. Old IDs keep resolving in the REST API, so background syncs keep working in the meantime.

Revoking access

Students can see and revoke connected apps in their UniClubs settings. A revoked app’s refresh tokens stop working, and the student sees the consent screen again on next sign-in.