api.colorstackatgsu.com · OAuth 2.1 / OpenID Connect

Sign in with ColorStack at GSU

Let a ColorStack at GSU member sign into your site with one click, and receive the profile fields they agree to share — name, email, LinkedIn, GitHub, verified Discord handle — instead of asking them to type it all again.

Before you start

You need a client id and client secret, which an officer issues. Email official@colorstackatgsu.com with:

Why it is not self-service

There is no dynamic registration endpoint. This database holds members' resumes, and a registry anyone could add themselves to would be a resume harvester with a login button on it. Registration is a deliberate act by an officer.

Endpoints

Everything is discoverable. Point any conforming OpenID Connect library at the issuer and it will configure itself:

https://api.colorstackatgsu.com/.well-known/openid-configuration
PurposeURL
Issuerhttps://api.colorstackatgsu.com
Authorization/oauth2/authorize
Token/oauth2/token
User info/userinfo
Resume download/oidc/resume
Public keys/oauth2/jwks
Revocation/oauth2/revoke

Scopes and the claims they carry

Ask for the narrowest set that does your job. Consent is all or nothing: the member sees every field you asked for, with their own values, and either allows all of it or none. What you may ask for at all is fixed when an officer registers your site.

ScopeClaimsNotes
openid sub Required. sub is a stable UUID — key your user records on it, never on the email.
profile given_name, family_name, name, pronouns, major, class_year, grad_term, grad_year, school class_year is one of Freshman, Sophomore, Junior, Senior, Graduate Student. school is always Georgia State University.
email email, email_verified, personal_email email is the student address and is always verified — we only admit members who hold one, so you can skip your own confirmation mail.
phone phone_number Digits only. Often absent: members are never required to give one.
socials linkedin_url, github_url Either may be absent.
discord discord_username, discord_verified discord_verified is proof, not a self-report: true only when the member completed verification in the chapter server. Trust it and skip asking again.
demographics race_ethnicity An array of the 2024 OMB categories. A member who declined sends ["Prefer not to say"]; keep it as given. Absent if they have not answered.
resume resume_download_url, resume_filename, resume_uploaded_at Only from /userinfo, never in a token. Download the PDF yourself — see below.

Every claim except sub can still be missing, because a member may have left that field blank on their profile. Write your code to handle absence rather than assuming a field arrived because you asked for it.

The flow

  1. Send the member to the authorization endpoint

    PKCE is mandatory. Generate a code_verifier, store it in the session, and send its SHA-256 hash as the challenge.

    https://api.colorstackatgsu.com/oauth2/authorize
      ?response_type=code
      &client_id=your-client-id
      &redirect_uri=https://yoursite.example/api/auth/callback
      &scope=openid%20profile%20email%20socials
      &state=<random, stored in the session>
      &code_challenge=<base64url(sha256(verifier))>
      &code_challenge_method=S256
  2. The member signs in and consents

    If they have no portal session they sign in first. Then they see exactly which fields you asked for, with their own values, and choose Allow or Don't allow for the whole request. Nothing you can send skips this screen. Don't allow comes back to you as error=access_denied.

  3. They come back with a code

    Your redirect URI receives ?code=...&state=.... Check the state matches what you stored, or you have a CSRF hole. The code is single-use and expires in minutes.

  4. Exchange the code for tokens — server side

    This call carries your client secret, so it must happen on your server. Never in a browser, never in a mobile app binary.

    curl -X POST https://api.colorstackatgsu.com/oauth2/token \
      -u "your-client-id:your-client-secret" \
      -d grant_type=authorization_code \
      -d code=<the code> \
      -d redirect_uri=https://yoursite.example/api/auth/callback \
      -d code_verifier=<the verifier you stored>

    You get back an access_token, a refresh_token, and an id_token. The ID token is a signed JWT already carrying the profile claims, so for name and email you can stop here.

  5. Call /userinfo when you need more

    curl https://api.colorstackatgsu.com/userinfo \
      -H "Authorization: Bearer <access_token>"

    Returns the same claims, plus the ones deliberately kept out of tokens. Verify the ID token's signature against /oauth2/jwks before trusting it — any standard library does this for you.

Resumes

With the resume scope granted, /userinfo returns a stable resume_download_url. Fetch the PDF from it with the same access token and store your own copy:

curl https://api.colorstackatgsu.com/oidc/resume   -H "Authorization: Bearer <access_token>"   -o resume.pdf

You get application/pdf with a Content-Disposition naming the member's own file. A token without the resume scope gets 403; a member with no resume on file gets 404.

Keeping your copy current

resume_uploaded_at moves when the member replaces their resume. Store it alongside your copy and re-download when it changes, or you will be showing recruiters a version the member has already replaced.

What holding a copy means

Once you have the bytes, they are yours, and a member deleting their resume in the portal does not delete it from your site. Nothing in this protocol can make it.

So that becomes your obligation rather than ours: tell members plainly that you keep a copy, and give them a way to remove it. Delete copies for members who revoke your access.

Token lifetimes

TokenLivesNotes
access_token15 minutesShort because it opens /userinfo, which can mint a resume link.
refresh_token30 daysRotated on every use. Store the new one each time.
authorization codeminutes, single useReplaying one invalidates the whole authorization.

Because refresh tokens rotate, a replayed one is treated as a compromise and the authorization is dropped. If you see members getting signed out unexpectedly, check that you are persisting the newest refresh token rather than the original.

When things fail

You seeIt usually means
invalid_redirect_uriYour redirect URI does not match the registered one byte for byte. Check scheme, port and trailing slash.
invalid_grantThe code expired, was already used, or the code_verifier does not match the challenge you sent.
invalid_clientWrong client secret, or it was not sent. Use HTTP Basic auth or form fields, not a query string.
invalid_scopeYou asked for a scope this client is not allowed. The registration is the ceiling; email an officer to widen it.
A claim is missingThe member left that field blank on their profile. It is normal — handle absence.

What members can do

A member can revoke your site's access at any time from the portal. When they do, your refresh token stops working and the next refresh fails. Treat that as a sign-out, not an error to retry — and do not silently re-prompt them to reconnect.

Not every GSU student is a ColorStack member. Keep an ordinary email sign-up alongside this button, or you will lock out most of the people you are trying to reach.