gemini-hlsw / lucuma-sso

Lucuma Single Sign-On Service and Libraries

Version Matrix

LUCUMA-SSO

Single sign-on service and support libries for Lucuma.

Web Client Flow

Initialization

  • Post to /api/v1/refresh-token
    • If you get a 403 Forbidden you are not logged in.
      • Continue with Login below.
    • If you get a 200 Ok
      • You are logged in.
      • The response body will contain a new JWT.
      • Set a timer to run Initialization again one minute before the JWT expires.
      • Continue with Normal Operation below.

Login

The user must be allowed to choose to log in with ORCID or log in as a guest.

Guest Login

  • Post to /api/v1/auth-as-guest
    • The response will be 201 Created.
    • The body will contain a JWT.
    • An http-only refresh cookie will be set.
    • Continue with Normal Operation below.

ORCID Login

  • Perform a client-side redirect to /auth/v1/stage1?state=APP_URI
    • On success the user will be redirected to APP_URI.
      • An http-only refresh cookie will be set.
      • Continue with Initialization above.

Normal Operation

  • Store the JWT in a variable and pass it to all API requests in the header as Authorization: Bearer <jwt>.
  • Decode the JWT body as a lucuma.core.model.User. A circe codec is provided by the lucuma-sso-frontend-client artifact.
  • If the user has insufficient privileges to view the application, there are three possibilities that should be presented.
    • If the user is currently a Guest
      • allow the user to upgrade their guest account (ORCID Login above).
    • If the user has other roles that are sufficient (via the otherRoles member on StandardUser)
      • Provide a menu that allows the user to select one of these roles
      • Continue with Set Role below.
    • Offer the option to log out.
      • Continue with Log Out below.
  • Display a user menu with the user's displayName shown.
    • If the user is a guest, offer an option to log in via ORCID.
    • If the user is a standard user, offer the option to change role.
    • Offer an option to log out.

Log Out

  • POST to https://sso.lucuma.gemini.edu/api/v1/logout
  • POST to https://orcid.org/userStatus.json?logUserOut=true (we need to test this)
  • Continue with Login above.

Set Role

This is not implemented yet.

  • Post to /api/v1/setRole?role=<role-id> to switch the user's current role.
    • Be sure to pass the Authorization header.
    • The response body will contain a new JWT.
      • Continue with Normal Operation above.

Back-End Service Workflow

  • See the backend-client-example module for an example of what follows.
  • Add lucuma-sso-backend-client as a dependency.
  • Ensure that your app is provided with the following configuration information:
    • Your SSO server's root URL (https://sso.foo.com for example)
    • Your SSO server's public key in GPG ASCII-armored format. For now you can grab this from the SSO server's Heroku configuration. We may change this to use a keyserver.
    • Your service JWT (see below).
  • Construct an SsoClient using this configuration and make it available to your HTTP routes.
  • Use the SsoClient to extract the requesting user from the http Request as needed.

Obtaining a Service JWT

Each back-end service must have its own service JWT for communicating with other services. SsoClient must communicate with SSO to exchange API tokens, so SsoClient users need a service token. You can obtain one by running a one-off Heroku dyno command from your SSO server. The service-name argument is arbitrary but should identify your application for logging purposes (observing-database, facility-service, etc).

heroku run -a <sso-app-name> create-service-user <service-name>

Discussion

It is possible to implement authentication as a middleware, but this makes composition of routes via <+> difficult because the middleware can either (a) reject unauthorized requests with 403 Forbidden, which means no following routes can possibly match; or (b) ignore unauthorized requests, which means the user will see a 404 Not Found instead of 403 Forbidden. So the recommended strategy for now is to check authorization on each route as described above.

Local Development QuickStart

Edit /etc/hosts to add local.lucuma.xyz as an alias of localhost.

127.0.0.1       localhost local.lucuma.xyz

Use docker-compose to wrangle a dev database. It's way easier than dealing with a real installation.

Command Description
docker-compose up start up the test database
docker-compose up -d start up the test database in the background
docker-compose run postgres psql -h postgres -d lucuma-sso -U jimmy start up a psql shell (password is banana)
docker-compose stop stop the test database
docker-compose down destroy the database

Docker-compose also starts up a local nginx server that serves an example client application at:

Working on the Schema

The app runs the migrations in /modules/service/src/main/resources/db/migration on startup.

Connecting to the Database

You can connect to youe dev database with locally-installed tools like pgAdmin and psql as follows. Note that it's important to explicitly specify localhost as the host, for example psql -h localhost -d lucuma-sso -U jimmy.

Parameter Value
host localhost
port 5432
database lucuma-sso
user jimmy
password banana

Setting up ORCID Credentials

If you try to run Main you will find that it barfs because it needs some ORCID configuration. To set this up, sign into ORCID as yourself, go to Developer Tools under the name menu and create an API key with redirect URL http://localhost:8080/auth/v1/stage2. This will allow you to test ORCID authentication locally.

You will need to provide GPP_ORCID_CLIENT_ID and GPP_ORCID_CLIENT_SECRET either as environment variables or system properties when you run Main. To do this in VS-Code you can hit F5 and then set up a run configuration as follows after which hitting F5 again should run SSO locally. Output will be in the Debug Console window.

{
  "version": "0.2.0",
  "configurations": [
    {
      "type"       : "scala",
      "request"    : "launch",
      "name"       : "Lucuma SSO",
      "mainClass"  : "lucuma.sso.service.Main",
      "args"       : [],
      "buildTarget": "service",
      "jvmOptions" : [
        "-DLUCUMA_ORCID_CLIENT_ID=APP-XCUB4VY7YAN9U6BH",
        "-DLUCUMA_ORCID_CLIENT_SECRET=265b63e5-a924-4512-a1e8-573fcfefa92d",
      ],
    }
  ]
}