JmpAPI authenticates users via OAuth2 (Google, Facebook, GitHub) or via a stored-procedure-driven password login. Both paths populate the same session, so the rest of the API treats them identically.
A request goes through three steps:
- The
sessionhandler attaches (or creates) a session cookie. See handlers.md. - A login endpoint (OAuth2 or password) authenticates the user and fills session variables and group memberships.
- Subsequent requests carry the session cookie;
{session.*}and{group.*}are available in SQL. See access-control.md.
For each provider, set three options in the top-level options: block:
options:
google-client-id: "..."
google-client-secret: "..."
google-redirect-base: https://example.com<provider>-client-id— public client ID from the provider.<provider>-client-secret— secret. Must be in a file readable only byroot(typically/etc/jmpapi/local.yaml).<provider>-redirect-base— base URL the provider redirects back to. Append the login path to get the full redirect URI that you register with the provider.
A provider is only enabled when all three options are set. GET /login/providers lists configured providers.
Wire one endpoint to handle all providers:
/login/{provider}:
args:
provider:
enum: [google, facebook, github, providers, '']
redirect_uri: {type: uri, optional: true}
get:
handler: login
sql: >
CALL AuthLogin({session.id}, {session.provider},
{session.provider_id}, {session.user}, {session.name},
{session.avatar})- Empty
{provider}returns the current session (or 401). providersreturns the list of configured providers.- A real provider name starts the OAuth2 dance and, on success, runs
the
sql:to record the login and populate the session. See Session SQL output.
redirect_uri (optional query arg) is where the client wants to land
after login.
- Go to https://console.cloud.google.com/apis/credentials.
- Create OAuth client ID → Web application.
- Set the authorized redirect URI to
<google-redirect-base>/login/google. - Copy the client ID and secret into
google-client-idandgoogle-client-secret.
- From your personal or organization settings, go to Developer Settings → OAuth Apps → New OAuth App.
- Set the authorization callback URL to
<github-redirect-base>/login/github. - Copy the client ID and secret into
github-client-idandgithub-client-secret.
- Go to https://developers.facebook.com/apps and create an app.
- Add the Facebook Login product.
- Under Facebook Login → Settings, add
<facebook-redirect-base>/login/facebookto Valid OAuth Redirect URIs. - Copy the App ID and App Secret into
facebook-client-idandfacebook-client-secret.
JmpAPI does not hash or verify passwords itself. The login handler forwards the plaintext password to a SQL stored procedure that does the verification.
/login:
get:
handler: login
provider: none # skip OAuth2; run the SQL directly
args:
email: {type: email}
password: {min: 8, max: 64}
sql: CALL AuthPasswordLogin({session.id}, {args.email}, {args.password})provider: nonedisables OAuth2 —sql:is called immediately with the validated args.- Always serve
/loginover HTTPS. The password is sent in clear text to the server.
The login sql: is expected to return multiple result sets:
- Session variables — a result set of
(name, value)rows. Each row setssession.<name>to that value (numbers preserved, everything else as string). Typical names:uid,user,name,avatar,provider,provider_id. - Groups — one or more additional result sets whose first column is a group name. Each row adds the user to that group.
On any error (bad credentials, disabled account), raise a SQL signal —
e.g. SIGNAL SQLSTATE '45000' for 400, or rely on ER_ACCESS_DENIED
for 401. See sql.md for the full error mapping.
Store a per-user random salt and a hash in the users table. A
reasonable schema:
ALTER TABLE users
ADD COLUMN password_hash CHAR(64), -- hex SHA-256
ADD COLUMN password_salt CHAR(32); -- hex randomUse a strong CSPRNG to fill password_salt at registration. Then
hash:
SET hash = SHA2(CONCAT(salt, plaintext), 256);SHA-256 with a per-user salt is fast — fine when paired with rate
limiting on /login and a strong password policy. For higher-value
applications, do a slow hash (bcrypt/argon2) in a small external
service called from your procedure, or in an app-side wrapper before
the password reaches MariaDB.
CREATE PROCEDURE AuthPasswordLogin(
IN sid VARCHAR(48), IN _email VARCHAR(128), IN _password VARCHAR(64))
BEGIN
DECLARE _uid INT;
DECLARE _name VARCHAR(128);
DECLARE _avatar VARCHAR(256);
SELECT u.id, u.name, u.avatar INTO _uid, _name, _avatar
FROM users u
WHERE u.email = _email
AND u.enabled
AND u.password_hash = SHA2(CONCAT(u.password_salt, _password), 256);
IF _uid IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid login';
END IF;
-- Bind session to user
REPLACE INTO sessions (id, uid) VALUES (sid, _uid);
-- Result 1: session variables
SELECT 'uid' AS name, _uid AS value
UNION ALL SELECT 'user', _email
UNION ALL SELECT 'name', _name
UNION ALL SELECT 'avatar', _avatar;
-- Result 2+: groups
SELECT g.name FROM groups g
JOIN user_groups ug ON ug.gid = g.id
WHERE ug.uid = _uid;
ENDRegistration and "change password" endpoints are normal query
endpoints — nothing special from JmpAPI's side. They should:
- Generate a fresh random salt server-side (in the SQL procedure or before).
- Store
SHA2(CONCAT(salt, plaintext), 256)along with the salt. - Never log the plaintext password.
The auto-register row in the config table (created by
scripts/update_db.py) is a convenient feature flag for whether
self-registration is open.
/logout:
put:
handler: logout
sql: CALL AuthLogout({session.id})Both OAuth2 and password sessions are closed the same way.
The default schema's users + associations tables let one user link
multiple OAuth2 providers, but the users table holds the password
hash. A user can therefore log in via any linked OAuth2 provider
or with their email + password — the login handler doesn't care
which path filled the session.