Registering an OAuth application

This article describes how to register an application with ActivityInfo so that ActivityInfo users can grant it access to their accounts, and how to add the OAuth flow to your application with a standard OAuth library.

Register an application when other people sign in to it with their own ActivityInfo accounts. If you only need to reach your own data from a script or a reporting tool, use a Personal API Token instead.

ActivityInfo acts as an OAuth 2.1 authorization server and supports the authorization code flow with PKCE. Registration is open: you register your application over HTTP, no ActivityInfo account or approval is needed, and you receive a client_id in return. ActivityInfo issues no client secret. Every registered application is a public client that proves its identity with PKCE, so the same registration works for a web application, a desktop application, a mobile application, and a command-line tool.

Before you begin

Decide on the redirect URI your application uses. This is the address ActivityInfo sends the browser back to, with the authorization code, once the user approves your application. ActivityInfo accepts three forms:

  • https://your-app.example.org/callback for an application hosted on the web. Plain http:// is rejected for any host other than loopback, so an authorization code never travels unencrypted.
  • http://127.0.0.1:8721/callback, http://[::1]:8721/callback or http://localhost:8721/callback for a desktop application or command-line tool that listens on a local port. Any port number is accepted.
  • com.example.myapp://oauth/callback for a mobile or desktop application that registers its own URL scheme with the operating system. The scheme must contain at least one dot.

The redirect URI must not contain a fragment (#...) or user information (user:password@).

Step 1: Read the OAuth endpoints

ActivityInfo publishes its endpoints as an OAuth 2.0 Authorization Server Metadata document (RFC 8414). Read the document rather than hard-coding the endpoints, so that your application also works against a self-managed ActivityInfo server on its own domain.

curl https://www.activityinfo.org/.well-known/oauth-authorization-server

The response includes the values your OAuth library needs:

{
  "issuer": "https://www.activityinfo.org",
  "authorization_endpoint": "https://www.activityinfo.org/oauth/authorize",
  "token_endpoint": "https://www.activityinfo.org/oauth/token",
  "registration_endpoint": "https://www.activityinfo.org/oauth/register",
  "scopes_supported": ["activityinfo.read", "activityinfo.write"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}

Most OAuth libraries read this document for you when you give them the issuer URL.

Step 2: Register your application

Send a POST request to the registration endpoint with the name of your application and its redirect URIs. The request needs no authentication.

curl -X POST https://www.activityinfo.org/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
        "client_name": "Field Report Sync",
        "redirect_uris": ["https://your-app.example.org/callback"]
      }'
Field Description
client_name The name shown to users on the consent screen. Up to 200 characters.
redirect_uris One to five redirect URIs, each up to 2048 characters.

ActivityInfo replies with status 201 and the registration:

{
  "client_id": "sQ8vK2mR7pX4nB9dL6tY3wF1jH5gC0aZ",
  "client_id_issued_at": 1767225600,
  "client_name": "Field Report Sync",
  "redirect_uris": ["https://your-app.example.org/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

Step 3: Store the client_id

Store the client_id in your application's configuration and reuse it for every user. Register once per application, not once per user.

The client_id is not a secret, and the response contains no client secret: token_endpoint_auth_method is none. Instead of a secret, each authorization request carries a PKCE code challenge, and ActivityInfo only releases the access token to the party that can produce the matching code verifier.

A registration that no user has ever approved may be removed after 30 days. If your client_id stops being recognised before you have any users, register again.

Step 4: Add the flow to your application

You do not need to implement the flow by hand. ActivityInfo follows OAuth 2.1, so any maintained OAuth client library handles it once you supply four values:

  • the authorization endpoint and the token endpoint from Step 1,
  • your client_id from Step 2,
  • one of your registered redirect URIs, and
  • the scope you are asking for: activityinfo.read, or activityinfo.write if your application needs to make changes.

Two requirements shape which library you can use. The library must support PKCE with the S256 method, which ActivityInfo requires, and it must support public clients, because there is no client secret to send. These libraries meet both:

Language Library
Python Authlib, requests-oauthlib
Node.js openid-client
Browser JavaScript oauth4webapi
Java, Kotlin Nimbus OAuth 2.0 SDK, Spring Security OAuth2 Client
.NET IdentityModel.OidcClient
Go golang.org/x/oauth2
PHP league/oauth2-client
Ruby oauth2
R httr2

Some libraries enable PKCE by default and others take it as an option, so check that S256 is switched on. Set the token endpoint authentication method to none where the library asks for one.

The following example uses Authlib in Python. The equivalent in another library is the same three calls: build the authorization URL, receive the code on your redirect URI, then exchange the code together with the code verifier.

from authlib.common.security import generate_token
from authlib.integrations.requests_client import OAuth2Session

AUTHORIZE_URL = "https://www.activityinfo.org/oauth/authorize"
TOKEN_URL = "https://www.activityinfo.org/oauth/token"

session = OAuth2Session(
    client_id="sQ8vK2mR7pX4nB9dL6tY3wF1jH5gC0aZ",
    redirect_uri="http://127.0.0.1:8721/callback",
    scope="activityinfo.write",
    code_challenge_method="S256",
    token_endpoint_auth_method="none",
)

# 1. Send the user to this URL and keep the verifier and the state.
code_verifier = generate_token(48)
authorization_url, state = session.create_authorization_url(
    AUTHORIZE_URL, code_verifier=code_verifier
)

# 2. Your redirect URI receives ?code=...&state=... Check that the state matches.

# 3. Exchange the code for tokens.
token = session.fetch_token(TOKEN_URL, code=code, code_verifier=code_verifier)

Send the access token from the token response as a bearer token on every API request, as described in Authenticate:

Authorization: Bearer <ACCESS TOKEN>

Refreshing the access token

An access token expires one hour after ActivityInfo issues it. The token response also contains a refresh token, which your application exchanges for a new access token without involving the user. Send the client_id with the refresh request:

curl -X POST https://www.activityinfo.org/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=<REFRESH TOKEN> \
  -d client_id=<CLIENT ID>

ActivityInfo returns a new access token and a new refresh token, and the refresh token you sent stops working. Store the new refresh token in place of the old one. Presenting a refresh token that has already been exchanged is treated as a sign that the token was stolen: ActivityInfo revokes the user's authorization, and the user has to approve your application again.

What your users see

The first time a user opens your authorization URL, ActivityInfo asks them to sign in, then shows the “Authorize application” screen. The screen leads with the destination that receives the authorization code, because ActivityInfo verifies that destination but cannot verify a name that anyone may register. Your client_name appears below it, marked as unverified.

The user then chooses what to grant. Read access is always part of the request. If you ask for activityinfo.write, the user sees an additional checkbox for making changes, which they can clear to grant read access only. So always check the scope in the token response, and handle a token that came back with only activityinfo.read. Ask for the scope your application needs: if you send no scope at all, ActivityInfo asks the user for write access.

The approved application appears on the “Authorized Applications” page of the user's account settings, with the scope they granted and the date it was last used. The user can revoke it from that page at any time. After a revocation your access token and refresh token both stop working, and your application has to send the user through the flow again.

Reference

Setting Value
Authorization endpoint https://www.activityinfo.org/oauth/authorize
Token endpoint https://www.activityinfo.org/oauth/token
Registration endpoint https://www.activityinfo.org/oauth/register
Grant types authorization_code, refresh_token
PKCE Required, method S256
Client authentication None. Public clients only
Scopes activityinfo.read, activityinfo.write
Authorization code lifetime 2 minutes, single use
Access token lifetime 1 hour
Refresh token lifetime 30 days, rotated on every use
Redirect URIs per application 5
Registrations per hour per network address 20

Troubleshooting

Error Cause
invalid_redirect_uri from /oauth/register The redirect URI uses a scheme other than https://, http:// to loopback, or a reverse-DNS scheme; or it contains a fragment or user information.
invalid_client: unknown client_id from /oauth/authorize The client_id was never registered, or its registration was removed because no user had approved it. Register again.
redirect_uri does not match a registered URI The redirect URI in the authorization request differs from the registered value. ActivityInfo compares the whole URI exactly, including the port and the path.
PKCE with code_challenge_method=S256 is required The authorization request sent no code_challenge, or used the plain method. Switch PKCE on in your library.
invalid_grant: PKCE verification failed The code verifier sent to the token endpoint does not match the code challenge sent to the authorization endpoint. Keep the verifier for the whole flow, one per authorization request.
invalid_grant: Unknown, expired or already-used code The authorization code is more than 2 minutes old, or it was already exchanged. Codes are single use, even when the exchange fails.
invalid_request: Missing client_id from a refresh request Add client_id to the refresh token request.
401 Unauthorized from the API The access token expired after an hour, or the user revoked your application. Refresh the token, and send the user through the flow again if the refresh also fails.
Next item
API Reference