Eternaltwin for OAuth
Technical information
- Authorization endpoint:
/oauth/authorize - Token endpoint:
/oauth/token - Both live at the root of the Eternaltwin website, not under
/api/v1. - Client ID: your client's
Key(<app key>@clients) or itsId(a UUID). TheKeyis recommended. - Client secret: as configured in your
eternaltwin.tomlfile.
External documents
- RFC 6749 - The OAuth 2.0 Authorization Framework
- Auth0 documentation
- Twinoid documentation
- OAuth, the reference page for both sides of OAuth in Eternaltwin
Registering the client
The first step to use Eternaltwin as an OAuth provider is to register your game or app as a client.
This step is achieved through the eternaltwin.toml configuration file (there is no web interface yet).
You must add an app section under [seed.app] and restart Eternaltwin.
Follow the documentation in the eternaltwin.toml file. Here is an example:
[seed.app.myproject] display_name = "My Project" uri = "http://localhost:8080/" oauth_callback = "http://localhost:8080/oauth/callback" secret = "dev_secret" allowed_scopes = "base"
The key myproject is used as an internal identifier for your project. It also
becomes your client key, so it must match [a-z_][a-z0-9_]{1,31}: lowercase
letters, digits and underscores, starting with a letter or an underscore. An
invalid key stops the server at startup rather than being ignored.
The display_name and uri are meant to identify your app to users: they are
both shown on the consent screen, next to your homepage's favicon. The display
name must match [A-Za-z_ ()-]{2,32} — letters, spaces, underscores,
parentheses and hyphens, but no digits.
The oauth_callback value is the absolute URI to your OAuth callback endpoint:
the URI where users are redirected back to your app with their authorization
code.
secret defines the secret key shared between Eternaltwin and your OAuth
client. It is used when exchanging the authorization code for an access token.
When running your project locally, it is recommended to leave it as
dev_secret. When running in production, the secret is a long random string.
allowed_scopes is the ceiling on what your app may request: a space-separated
list of base, forum:read, forum:write and forum:moderate. Omitting the
key leaves the app's current allowance alone rather than resetting it, and an app
that was never given one may only request base. See
The forum from an application.
An optional id key pins the client's UUID. Without it, Eternaltwin generates
one the first time the client is created and keeps it in the database; it only
changes if the database is reset. This is why the Key is the better
client_id: it is derived from your section key and never moves.
Restart your local Eternaltwin server to apply the changes. Each seeded client is reported on startup:
- upsert oauth client "myproject"
The client key is that key followed by @clients, so the example above is
reachable as myproject@clients. The OAuth protocol refers to a client_id
value used to identify your client; Eternaltwin accepts either the client key or
the client UUID there.
Acquiring the Access token
OAuth is a standard protocol. You should check if your language has existing libraries to help you acquire the access token.
User redirection
Add a form on your app containing a single button Sign-in with Eternaltwin
and no text field (you may add some hidden fields as needed).
Clicking on this button should submit the form through POST to your own server.
The server should reply to this request with a redirection to the
Eternaltwin authorization endpoint: HTTP status code 302 with a Location
header.
The redirection URL is built with the following parameters:
| Name | Value |
|---|---|
| Origin | The origin of the Eternaltwin website: https://eternaltwin.org/ in production, http://localhost:50321/ for a local yarn start (or http://localhost:4200/ when the frontend runs through ng serve). Your app should get this value from the environment (config file, environment variable) |
| Pathname | /oauth/authorize |
response_type parameter | Required. The string code. token is recognised but not implemented, and is answered with 501 |
client_id parameter | Required. Your client's Key (recommended) or Id |
redirect_uri parameter | Optional. If present, it must be exactly the oauth_callback registered for the client; a mismatch is refused |
scope parameter | Optional. The permissions to request, space-separated. An omitted or empty value means base, that is "identity only"; see The forum from an application for the forum scopes. Your app may never request more than its allowed_scopes |
state parameter | Optional. A string holding your application state, returned as-is. It is recommended to use a signed JWT |
No other parameter is read. In particular, access_type=offline is accepted and
ignored — it is a Google extension, and Eternaltwin issues no refresh tokens —
and PKCE (code_challenge, code_verifier) is not supported.
The origin must be the website, not the backend port: this endpoint drives a
browser and sends it to Eternaltwin pages (/login, /tos-accept, /consent)
that only the website serves.
Example:
http://localhost:50321/oauth/authorize?response_type=code&client_id=myproject%40clients&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth%2Fcallback&scope=&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uU2VydmVyIjoiZXRlcm5hbC10d2luLm5ldCIsInJlcXVlc3RGb3JnZXJ5UHJvdGVjdGlvbiI6ImJkZWQyZDg5MWFlNDYwMTk2OWZhZmI0YjAxMmQ3ODZiIiwiaWF0IjoxNjA3OTU0NDExLCJleHAiOjE2MDgwNDA4MTF9.BRvm4D4Rfc2ZoHwlzLtEd3oiyJmxCq4eqPmxhYXRz7g
Sign-in and consent
Reaching the authorization endpoint does not send the user straight back to your app. Eternaltwin may first take one of these detours, each of which returns to the authorization request once it is done:
- a signed-out user is sent to
/login; - a user who has not accepted the terms of service is sent to
/tos-accept; - a user who has not yet approved the requested scopes for your client is sent
to the consent screen at
/consent.
The consent screen names your app and lists what it is asking for. The answer is
remembered per user and per client, so a later request covered by it goes
through without interrupting anyone; a request for a wider set asks again. From
your app's point of view nothing changes: you send the user to
/oauth/authorize and they come back to your callback.
User return
Once Eternaltwin has authenticated the user and the user has approved the
request, the browser is redirected back to your app at the URI registered as
oauth_callback. The redirect_uri parameter never changes where the user is
sent; it is only checked against the registration.
Eternaltwin will append the following search parameters:
- In case of success:
code: The one-time authorization codestate, if you sent one
- If the user refuses the request:
error: the stringaccess_denied(RFC 6749 §4.1.2.1)state, if you sent one
A refusal is the only error redirected to your callback. A malformed request —
unknown client_id, mismatched redirect_uri, missing response_type, scope
above your allowance — is answered on Eternaltwin itself with an HTTP error
status and a JSON error object, and the user is never redirected to your app.
See OAuth for those error codes.
Example (success):
http://localhost:8080/oauth/callback?code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiMzhmMzNjM2YtZGIzYi00OWNlLTgxYTctNTk3Yzk3YmEzMTYyIiwibXlwcm9qZWN0QGNsaWVudHMiXSwiZXhwIjoxNzU3NDMwMDAwLCJpYXQiOjE3NTc0Mjk0MDAsImlzcyI6ImV0ZXJuYWx0d2luIiwibmJmIjoxNzU3NDI5NDAwLCJzdWIiOiJkMTYxNjRhNC1hODliLTRhYzUtOGNkYS03ZDU1ZjkzMWFkYjgiLCJ0cnAiOiIwMC0wYWY3NjUxOTE2Y2Q0M2RkODQ0OGViMjExYzgwMzE5Yy1iN2FkNmI3MTY5MjAzMzMxLTAxIiwic2NvcGVzIjpbImJhc2UiXX0.t1Qx0yXqk7Yw2m5JrO8bV3dZcQhLp9sA6uNfEgKiRtY&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemF0aW9uU2VydmVyIjoiZXRlcm5hbC10d2luLm5ldCIsInJlcXVlc3RGb3JnZXJ5UHJvdGVjdGlvbiI6ImJkZWQyZDg5MWFlNDYwMTk2OWZhZmI0YjAxMmQ3ODZiIiwiaWF0IjoxNjA3OTU0NDExLCJleHAiOjE2MDgwNDA4MTF9.BRvm4D4Rfc2ZoHwlzLtEd3oiyJmxCq4eqPmxhYXRz7g
Claiming the token
On success, your callback handler receives a one-time authorization code
(code). You can exchange this code for an access token. The code is a signed
JWT, valid for 10 minutes; treat it as an opaque string.
You app must perform a direct request to the Eternaltwin server (not a client redirection).
| Name | Value |
|---|---|
| Method | POST |
| Origin | Same origin as the authorization endpoint |
| Pathname | /oauth/token |
Authorization header | Scheme: Basic, login: OAuth client Key or Id, password: secret field from the eternaltwin.toml |
Content-type header | application/json or application/x-www-form-urlencoded. It is required: any other value is answered with 415 |
code request body field | The value of the one-time authorization code |
grant_type request body field | The string authorization_code. Eternaltwin ignores it, but send it anyway: it is what the RFC requires and what generic OAuth libraries send |
Accepting a JSON body is an Eternaltwin extension to the OAuth specification.
The Eternaltwin server will respond with the access token:
{
"token_type": "Bearer",
"access_token": "e2d9f1b0-6d47-4f0a-9a3f-1c7c0a5f4b21",
"expires_in": 1000000000
}
The access token is a UUID identifying a token stored by Eternaltwin, together
with the scopes the user approved. There is no refresh_token field: Eternaltwin
does not issue refresh tokens. expires_in is about a billion seconds, which is
how access tokens currently say "no expiry"; do not build on that number staying
what it is.
The value of the access_token field is the one you should use with API clients.
Next steps
The next step is usually to immediately use this access_token to get data about
the current user from the API and authenticate it.