Skip to content

Let colleagues sign in to your application

A registered application is a web application your organization builds or uses that acts for a colleague, under that colleague’s own access, after they approve it. A map viewer, a report generator or a team dashboard are typical ones. The application never sees a password or an Ophiolite session: it gets a credential that works only for the person who approved it, only in the projects they chose, and only until they remove it.

Signing in to a registered application involves two parties, and the person meets each once:

[colleague's browser]
| 1. sign in at your identity provider (code + PKCE, the application's own client)
v
[identity provider] --code--> [application's server]
| 2. approve the application on Ophiolite's consent page
v
[Ophiolite consent page] --consent handle--> [application's server]
| 3. exchange: provider token + handle
v
[Ophiolite API] --> credential (minutes)

What to notice: the identity provider says who the person is; Ophiolite’s consent says what the application may do for them. Neither alone is enough, and removing the approval in Workspace ends the application’s access at its next exchange, even while the person is still signed in at the provider.

The application’s server keeps the provider’s refresh token, the consent handle and the short-lived Ophiolite credential. The browser holds only the application’s own session cookie. A credential lasts minutes; the server exchanges again when it expires, so a removed approval or a suspended application takes effect within that time.

  1. In Workspace, open your organization and choose Applications.
  2. Give it a name colleagues will recognise, and its return addresses: the exact https:// addresses of its callback page, one per line.
  3. Choose what it may do: Can read, or Can read and publish.
  4. Leave It has its own server that can keep a secret ticked for a web application with a server. Copy the secret when it is shown; it is shown once. If it is lost, choose Make a new secret.

An owner can Suspend an application at any time; nobody can then sign in to it, and every colleague’s approval of it ends. Let members sign in again reverses the suspension; colleagues approve it again.

Some deployments also accept registration from the application itself (OAuth 2.0 dynamic client registration, RFC 7591, at the address in /.well-known/oauth-authorization-server). Such an application waits as Registered itself — waiting for an owner to allow it until an owner chooses Allow this application.

The first time you sign in to the application, Ophiolite asks Let … act for you?, says what it can do, and lets you choose All projects you can open, now and later or Only the projects I choose. Choose Allow or Don’t allow.

You see every application that acts for you under Account security → Apps you use. Remove access ends it at the application’s next exchange.

The Python SDK’s ophiolite.oauth_app.OAuthApp performs both steps and the exchange, and keeps each person’s sign-in in a private folder on the server. The smallest working server is registered_application.py; its sign-in part:

app = OAuthApp(args.workspace, args.application, args.client_id, args.public_origin + '/callback',
store=FileStore(args.store), client_secret=os.environ.get('OPHIOLITE_CLIENT_SECRET'))
sessions = {} # this server's own sessions: a random cookie value -> who signed in, and pending sign-ins
def answer(path, query, session):
if path == '/sign-in': # 1. the identity provider (code + PKCE)
address, session['pending'] = app.begin()
return 302, address
if path == '/callback' and 'code' in query: # 2. Ophiolite's consent page
session['person'] = app.callback(session.pop('pending', None), query.get('code'), query.get('state'))
session['consent'] = secrets.token_urlsafe(24)
return 302, app.consent_url(session['consent'])
if path == '/callback': # 3. the exchange, on this server
app.consented(session.get('person'), session.pop('consent', None), query)
return 302, '/'
if path == '/projects' and session.get('person'): # a call made for the person, from this server
try:
with app.client(session['person'], args.project) as client:
return 200, json.dumps({'wells': len(client.wells())})
except (SignInRequired, ConsentRequired):
return 302, '/sign-in'
return 404, json.dumps({'message': 'Not found'})

Run it with the registration’s id and client id, and the secret in the environment:

Terminal window
export OPHIOLITE_CLIENT_SECRET=... # shown once when the application was registered
python registered_application.py --workspace https://workspace.example \
--application <registration id> --client-id <client id> \
--public-origin https://maps.example --project <project id>

The map application template (ophiolite init map-application) carries the same flow in server/sign_in.py, with sign-out and its tests.

  • The browser never carries a token. Responses, redirects and cookies from the application’s server carry only its own session cookie, marked Secure, HttpOnly and SameSite=Lax.
  • The exchange is server to server. POST /api/v1/applications/exchange refuses a request with a cookie, an Origin header or an Authorization header, so a browser cannot call it, even by mistake.
  • The identity provider is the deployment’s. OAuthApp reads the provider from /.well-known/oauth-authorization-server on your Workspace address and refuses endpoints on any other origin.
  • Return addresses match exactly. A return address that was not registered is refused by the identity provider and by Ophiolite’s consent page.
  • The application’s own calls stay on its origin. A page of the application calls its own server, which calls Ophiolite; the template refuses a call whose Origin is not its own.
What happenedWhat the application seesWhat it does
The colleague removed it in Apps you useConsentRequired at the next exchangeSend them to sign in again; they approve again
An owner suspended the applicationConsentRequiredNobody can sign in until an owner lets members sign in again
The provider session endedSignInRequiredSend them to sign in again; their approval stays
The credential expirednothingOAuthApp exchanges again on its own

For the endpoints, see the Application API reference.