Your first application
In this tutorial you register an application at Authora and sign in to it, by hand, with the Authorization Code flow and PKCE: the same steps an OpenID Connect library performs for you. You will see the consent screen, exchange a code for tokens, read the ID Token and the UserInfo endpoint, and watch Authora detect a stolen Refresh Token.
You only need a browser and a terminal with curl and openssl. It takes about fifteen minutes.
Before you start: complete Getting started, so Authora runs at https://localhost:8443 and you can sign in to the admin interface.
1. Register the application
Applications that sign in at Authora are called Clients. Register one:
Open https://localhost:8443/admin/#/clients and click Register Client.
Fill in the form:
Field Value Name My first appType Public – a browser, mobile or desktop app without a SecretGrant Types leave both checked: Authorization CodeandRefresh TokenRedirect URIs http://127.0.0.1:8080/callbackAllowed Scopes check openid,profileandemailClick Register Client.
Authora opens the new Client. Copy its Client Id and keep it in your terminal:
CLIENT_ID=paste-the-client-id-hereA few things to note:
- A Public Client has no Secret, like a mobile app or a single-page application. It proves that it started the sign-in with PKCE instead, which you set up next.
- The Redirect URI is where Authora sends the browser back. Authora only redirects to registered Redirect URIs, and they must match exactly. Plain HTTP is only allowed for
127.0.0.1and[::1], so local test apps work. - Allowed Scopes limits what the Client may ask for.
openidmakes this an OpenID Connect sign-in,profilereleases the name andemailthe Email address.
2. Create a PKCE pair
PKCE ties the code Authora issues to the application that asked for it. The application makes up a random secret, the code verifier, and sends only its SHA-256 hash, the code challenge, with the sign-in request. When it exchanges the code, it sends the verifier, and Authora checks that it matches.
CODE_VERIFIER=$(openssl rand -base64 48 | tr -d '=+/\n' | cut -c1-64)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
echo "$CODE_CHALLENGE"3. Send the user to Authora
Build the authorization URL:
echo "https://localhost:8443/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Fcallback&scope=openid%20profile%20email&state=tutorial-state&nonce=tutorial-nonce&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256"Open the printed URL in the browser where you are signed in to the admin interface.
This time Authora asks: My first app wants to access your account. It lists what each Scope releases, and where you will be sent back. My first app is a third-party Client, so Authora asks on every sign-in. You did not see this for the admin interface, because that is a first-party Client.
You did not have to sign in again: you already have a Session at Authora from the admin interface. That is single sign-on.
4. Get the code
Authora issues a code that is valid for only one minute. Prepare the next command first, so you are quick:
CODE=paste-the-code-hereNow click Allow.
The browser goes to http://127.0.0.1:8080/callback?code=…&state=tutorial-state&iss=https%3A%2F%2Flocalhost%3A8443 and shows an error, because no application listens on port 8080. That is fine: copy the value of code from the address bar into the command above and run it.
A real application would now check two values in that URL: state must be the value it sent, and iss must be Authora's Issuer. Both protect against forged responses.
5. Exchange the code for tokens
curl -k https://localhost:8443/oauth/token \
-d grant_type=authorization_code \
-d client_id="$CLIENT_ID" \
-d code="$CODE" \
-d redirect_uri=http://127.0.0.1:8080/callback \
-d code_verifier="$CODE_VERIFIER"If the minute has passed, you get {"error":"invalid_grant", …}. Go back to step 3 and try again: you still have your Session, so it is only a click.
Otherwise, the answer looks like this:
{
"access_token": "ozklvvXHJoSeuvQ5X3z9mLdFedOsjX5ORW8VanpmKT0",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "pqIxxPL21_MkKSDLLLLyqR4CL6CnYU-qI5ZeimX8K1Q",
"scope": "openid profile email",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6…"
}Copy the three tokens into your terminal:
ACCESS_TOKEN=paste-access-token
REFRESH_TOKEN=paste-refresh-token
ID_TOKEN=paste-id-token6. Read the ID Token
The ID Token tells the application who signed in. It is a JSON Web Token: three Base64url parts separated by dots, the middle one holding the claims. Decode it:
python3 -c 'import sys,json,base64; p=sys.argv[1].split(".")[1]; print(json.dumps(json.loads(base64.urlsafe_b64decode(p+"="*(-len(p)%4))),indent=2))' "$ID_TOKEN"{
"sub": "01a1542c-e0b2-7896-b701-241c6b7e3b7b",
"name": "Authora Administrator",
"given_name": "Authora",
"family_name": "Administrator",
"email": "admin@example.com",
"email_verified": false,
"iss": "https://localhost:8443",
"aud": "01a1542d-905a-7baf-82b2-e2db33513106",
"iat": 1792413634,
"exp": 1792413934,
"auth_time": 1792413634,
"at_hash": "bG4HA6mGMOFDblaAba54wg",
"nonce": "tutorial-nonce"
}subis your User Id. It never changes, so applications use it, not the Email, to recognize a user.audis your Client Id: the token was made for your application only.nonceis the value you sent in step 3, so the application can tell that this token answers its own request.- The ID Token expires after five minutes (
exp). It is meant to be checked once, at sign-in.
A real application also verifies the token's signature with Authora's public keys at https://localhost:8443/.well-known/jwks.json. OpenID Connect libraries do that for you.
7. Call UserInfo
The Access Token is different: it has no readable content. It is a random value that only Authora can look up. Use it to ask Authora about the User:
curl -k https://localhost:8443/oauth/userinfo -H "Authorization: Bearer $ACCESS_TOKEN"{"sub":"01a1542c-e0b2-7896-b701-241c6b7e3b7b","name":"Authora Administrator","given_name":"Authora","family_name":"Administrator","email":"admin@example.com","email_verified":false}8. Refresh the tokens
The Access Token expires after an hour. The Refresh Token gets a new one without bothering the user:
curl -k https://localhost:8443/oauth/token \
-d grant_type=refresh_token \
-d client_id="$CLIENT_ID" \
-d refresh_token="$REFRESH_TOKEN"You get a new Access Token and a new Refresh Token. The old Refresh Token is now used up. Authora rotates Refresh Tokens, so a stolen one is only useful until the real application uses it.
9. Watch Authora catch a replayed token
Pretend an attacker stole the old Refresh Token and uses it now:
curl -k https://localhost:8443/oauth/token \
-d grant_type=refresh_token \
-d client_id="$CLIENT_ID" \
-d refresh_token="$REFRESH_TOKEN"{"error":"invalid_grant","error_description":"The Refresh Token is invalid, expired or revoked."}Authora cannot know whether the attacker or the application holds the newer token, so it revokes the whole sign-in, called the Grant: every Access and Refresh Token issued from it. Authorization Codes are treated the same way: exchanging a code a second time also revokes its Grant. Even the first Access Token, which had not expired yet, stops working at once:
curl -k -i https://localhost:8443/oauth/userinfo -H "Authorization: Bearer $ACCESS_TOKEN"HTTP/2 401
www-authenticate: Bearer error="invalid_token"Open the Audit log in the admin interface: the event oauth.refresh-token-reused records the attack.
What you have learned
- An application is a Client with Redirect URIs and allowed Scopes.
- The Authorization Code flow: the browser brings back a short-lived, single-use code, and the application exchanges it, with the PKCE verifier, for tokens.
- The ID Token says who signed in. The Access Token is opaque and only Authora can check it, which is why revoking it works immediately.
- Refresh Tokens rotate, and replaying an old one revokes the whole Grant.
Next steps
- Connect an application shows the settings for a real OpenID Connect library.
- Protect an API with introspection lets your API check the Access Tokens.
- Tokens, Grants and Sessions explains the design behind what you just saw.