Last changed

Sign-in API

The contract a program is written against. If you are working on the MCCP app - the Moddable Console Capture Platform - this is the document you need; you do not need to read the site's source. (The repository still carries the old name in its address, which GitHub redirects; the name in these documents is the current one.)

Accounts belong to the site, not to your program. The same account signs in at dasmaffin.com/auth/login in a browser and here from an application, and a person can bind more than one way in to it. Your program never sees a password after the moment somebody types one.

Base address: https://dasmaffin.com. Everything below hangs off /api/auth.


1. Make an account

POST /api/auth/register
Content-Type: application/json

{ "email": "someone@example.com", "password": "a long enough one",
  "displayName": "Someone", "client": "MCCP 1.4 on DESKTOP-1234" }

displayName is optional. client is optional and is worth sending: it is stored against the refresh token, so a person can later recognise which machine is signed in.

201 with the same body as signing in, below. A letter goes to the address asking them to confirm it, and the account is signed in at once either way - confirming is not currently required to use it. If that changes you will see 403 on a later sign-in rather than a different answer here.

Code Meaning
400 Not an email address, or a password shorter than 10 characters
403 Registration is switched off
409 That address already has an account
429 Too many attempts from here; the body carries retryAfterSeconds

2. Sign in

POST /api/auth/login
Content-Type: application/json

{ "email": "someone@example.com", "password": "a long enough one", "client": "MCCP 1.4",
  "code": "123456" }

code is only wanted by accounts that have set up an authenticator app, and you do not have to know in advance which those are: send the password on its own, and if a code is needed the refusal says so. See below.

200:

{
  "accessToken": "CfDJ8...",
  "expiresInSeconds": 1800,
  "refreshToken": "kQ7x...",
  "accountId": 41,
  "displayName": "Someone"
}

Store the refresh token, never the password. The refresh token is what keeps somebody signed in between runs; the password should not survive the form it was typed into.

Code Meaning
401 No such address, or the wrong password. One answer for both, word for word - do not try to tell them apart, and do not word your own message as though you could
401 with "twoFactorRequired": true The password was right and a code from their authenticator app is missing or wrong. Ask for the code and post the whole thing again, password included
403 The address has not been confirmed (only possible once verification is switched on)
429 Too many attempts; retryAfterSeconds says how long

3. Use it

Authorization: Bearer <accessToken>

The access token is good for thirty minutes and cannot be withdrawn inside that window, which is why it is short. Do not try to decode it; treat it as opaque, because its contents are ours to change.

GET /api/auth/me

200 with accountId, kind (Steam or Password), subject, displayName, verified. 401 if the token has expired, was edited, or names a login that has since been deleted.


4. Stay signed in

POST /api/auth/refresh
Content-Type: application/json

{ "refreshToken": "kQ7x...", "client": "MCCP 1.4" }

200 with a fresh pair, exactly like signing in.

A refresh token works once. The one you sent is dead the moment this answers, and the answer carries its replacement - so store the new one before you do anything else with it. A crash between the two costs somebody a sign-in.

401 means expired, already used, or signed out. The only thing to do with it is ask for the password again.


5. Forgotten password

POST /api/auth/forgot
Content-Type: application/json

{ "email": "someone@example.com" }

204, always. A link to choose a new password is emailed if there is an account with that address, and the answer is the same when there is not - otherwise this endpoint is a way for anybody to find out which addresses are registered here.

The reset itself happens on the website, because the link goes to a mailbox and is opened in a browser. Your program's part is this call and a sentence telling them to go and read their mail. Once they have, the old password stops working and every refresh token on the account is dead, so expect a 401 on your next refresh and ask for the password again.

429 if it is asked too often, with retryAfterSeconds.


6. Sign out

POST /api/auth/logout
Content-Type: application/json

{ "refreshToken": "kQ7x..." }

204, always - including for a token that was never real, because answering differently would let anybody ask this endpoint whether a token they found is genuine. The access token keeps working until it expires; that is what a thirty-minute lifetime is for.


Things that will bite you if nobody says them

A person may have no password at all. Somebody who signed in through Steam on the website has an account with a Steam login and nothing else, and /api/auth/login will never accept them. They add a password at dasmaffin.com/auth/profile while signed in, and then both work and both lead to the same accountId. If your program has a "sign in" screen and no way to explain that, people who already have an account will be certain it is broken.

Handle twoFactorRequired or people with an authenticator app cannot sign in at all. It is the one thing a 401 here tells you that a wrong password does not. Read it, show a field for six digits, and post email, password and code together - there is no separate endpoint and nothing is remembered between the two attempts, so the password has to come with it. A wrong password never carries this flag, so it can never be used to find out who has a second factor.

A code works once. If somebody mistypes and you retry with the same digits, it will be refused even when it was right. Ask for the current ones.

accountId is the identity. Nothing else is. Not the email - a person can have several logins on one account, and the address is one of them. Store the id.

A password reset ends in a browser, not in your program. The link is emailed and opened there, because that is what proves the mailbox is theirs. POST /api/auth/forgot is the whole of your side of it - after that, tell them to go and read their mail.

Everything is counted. Ten failures per address and per caller in fifteen minutes, and both halves are counted at once. Retrying a wrong password in a loop locks the account out for everybody, including whoever is typing it correctly on the website. Back off on a 429 and show the retryAfterSeconds.

A password account holds no permissions here, and no module has to accept it. TGSC refuses them outright: its pages answer 403 to a password session, because everything it knows about a person is keyed on a SteamID64. That is a property of that module, not of the account.

The tokens are not JWTs. They are opaque strings signed by the site. Nothing in them is readable and nothing in them should be parsed.

The plain file: /docs/accounts/api.md