The reason we need this service is that a worker within a workspace is not tied to an active user web session, so there isn't an easy way for users within a worker to call Gen3 services other than manually copying the token into the worker.
The Gen3 workspace token service acts as an OIDC client which acts on behalf of users to request refresh tokens from Fence. This happens when a user logs into a workspace from the browser. WTS then stores the refresh token for that user, and manages access tokens and refresh tokens for workers that belong to specific users in the workspace.
OpenAPI Specification here.
- The workspace UI calls
/oauth2/authorization_url
to connect with Fence during user login, this will do an OIDC dance with fence to obtain a refresh token if it's a new user or if the user's previous refresh token is expired. - The worker calls
/token?expires=seconds
to get an access token
Two methods exist for authenticating a request to the /token
endpoint:
-
WTS
checks for the presence of a JWT access token in theAuthorization
header and validates said token. To prevent a user from being able to indefinitely generate access tokens using access tokens returned fromWTS
, this means of authentication is not available for requests with theidp
URL query parameter omitted oridp=default
. -
If a JWT access token is not provided, the
idp
parameter is omitted, oridp=default
, thenWTS
determines the current user from thegen3username
K8s annotation set for the requesting pod.
dbcreds.json
:
{
"db_host": "xxx",
"db_username": "xxx",
"db_password": "xxx",
"db_database": "xxx"
}
appcreds.json
:
{
"wts_base_url": "https://my-data-commons.net/wts/",
"encryption_key": "xxx",
"secret_key": "xxx",
"fence_base_url": "https://my-data-commons.net/user/",
"oidc_client_id": "xxx",
"oidc_client_secret": "xxx",
"aggregate_endpoint_allowlist" : ["/authz/mapping", "/user/user"],
"external_oidc": [
{
"base_url": "https://other-data-commons.net",
"oidc_client_id": "xxx",
"oidc_client_secret": "xxx",
"redirect_uri": "https://shared.redirect/whatever",
"login_options": {
"other-google": {
"name": "Other Commons Google Login",
"params": {
"idp": "google"
}
},
"other-orcid": {
"name": "Other Commons ORCID Login",
"params": {
"idp": "fence",
"fence_idp": "orcid"
}
},
...
}
},
{
"base_url": "https://non-fence-data.org/",
"oidc_client_id": "xxx",
"oidc_client_secret": "xxx",
"login_options": {
"externaldata-keycloak": {
"name": "keycloak Login",
"params": {
"idp": "keycloak",
"auth_url": "auth/realms/xyz/protocol/openid-connect/auth",
"token_url": "auth/realms/xyz/protocol/openid-connect/token",
"id_token_username": "email",
"scope": "openid profile offline_access"
}
}
}
},
...
]
}
The default OIDC client configuration (fence_base_url
, oidc_client_id
and oidc_client_secret
) is generated automatically during gen3 kube-setup-wts
. Other clients can be created by running the following command in the external Fence: fence-create client-create --client wts-my-data-commons --urls https://my-data-commons.net/wts/oauth2/authorize --username <your username>
, which returns a (key id, secret key)
tuple. Any login option that is configured in the external Fence (the list is served at https://other-data-commons.net/user/login
) can be configured here in the login_options
section.
Note that IDP IDs (other-google
and other-orcid
in the example above) must be unique across the whole external_oidc
block.
Also note that the OIDC clients you create must be granted read-storage
access to all the data in the external
Data Commons via the data-commons' user.yaml
.
The id_token_username
property for OIDC clients can be configured with .
in between strings for a nested username inside a token.
For example if the token jwt has username encoded in the json as token["context"]["user"]["name"]
:
We can write this in the parameters as "id_token_username": "context.user.name"
.
If nothing is specified, for a fence client the default is "context.user.name"
, for a non-fence client the default is "email"
.
The redirect_uri
property for external OIDC providers is
an optional field that supports sharing OIDC client
configuration between multiple workspace deployments
as part of a multi-account application system.
Finally, non fence IDPs can be provided given their auth url, token url, and necessary scope as a part part of the params
of the external IDP.
The key aggregate_endpoint_allowlist
is an optional key which consists of a list of endpoints that are supported by the /aggregate
api.
cat - > docker-compose.yml <<EOM
EOM
docker-compose up -d
psql -c 'create database wts_test;' -U postgres -h localhost
Local development like this:
pipenv shell
pipenv install --dev
pytest
Open a devterm
in the jupyter namespace:
gen3 devterm --namespace "$(gen3 jupyter j-namespace)" --user [email protected]
Interact with the WTS:
WTS="http://workspace-token-service.${NAMESPACE:-${KUBECTL_NAMESPACE##jupyter-pods-}}.svc.cluster.local"
curl $WTS/
curl $WTS/external_oidc/ | jq -r .
curl $WTS/token/?idp=default
First, login to the commons: https://your.commons/user/login/google?redirect=https://your.commons/user/user
Now try to acquire an access token for some idp: https://your.commons/wts/oauth2/authorization_url?idp=default
You can now deploy individual services via Helm! Please refer to the Helm quickstart guide HERE (https://github.com/uc-cdis/workspace-token-service/blob/master/docs/img/quickstart_helm.md)