Eternaltwin

Home | Applications

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 its Id (a UUID). The Key is recommended.
  • Client secret: as configured in your eternaltwin.toml file.

External documents

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:

NameValue
OriginThe 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 parameterRequired. The string code. token is recognised but not implemented, and is answered with 501
client_id parameterRequired. Your client's Key (recommended) or Id
redirect_uri parameterOptional. If present, it must be exactly the oauth_callback registered for the client; a mismatch is refused
scope parameterOptional. 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 parameterOptional. 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 code
    • state, if you sent one
  • If the user refuses the request:
    • error: the string access_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).

NameValue
MethodPOST
OriginSame origin as the authorization endpoint
Pathname/oauth/token
Authorization headerScheme: Basic, login: OAuth client Key or Id, password: secret field from the eternaltwin.toml
Content-type headerapplication/json or application/x-www-form-urlencoded. It is required: any other value is answered with 415
code request body fieldThe value of the one-time authorization code
grant_type request body fieldThe 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.

See using the Eternaltwin API.