Meadowlark - Internal OAuth 2 Client Credential Provider
- Stephen Fuqua
Overview
The Ed-Fi API needs to be secured with the OAuth 2.0 Client Credentials flow. In many cases a third-party authentication provider will be most appropriate for managing authentication. However, some organizations may wish to continue using a built-in authentication provider, as with the ODS/API Suite 3. In addition to managing the authentication process itself, the built-in provider should handle provisioning of keys and secrets. In this way, we will be able to have a micro data store that is accessed by only a single application.
Requirements
Authentication / Token Generation
Support Client Credentials flow
- Example route signature:
POST /oauth/token
- with support for the following message body formats:
grant_type
,client_id
, andclient_secret
in a JSON payloadgrant_type
,client_id
, andclient_secret
in a form-urlencoded payloadgrant_type
in a json payload, withclient_id
, andclient_secret
encoded into a basic authentication headergrant_type
in a form-urlencoded payload, withclient_id
, andclient_secret
encoded into a basic authentication header.
- with responses:
200 when the request is valid, with a signed JSON Web Token (JWT) as an access code response (more detail on JWT below). Example:
{ "access_token": "eyJ0eXAiOiJKV1QiLCJibGciOiJIUzI1NiJ9.eyJpc3MiOiJlZC1maS1tZWfkb3dsYXJrIiwiYXVkIjoibWVhZG93bGFyayIsInJvbGVzIjpbInZlbmRvciJdLCJzdWIiOiJzdXBlci1ncmVhdC1TSVMiLCJqdGkiOiIyODQxNTY3Yi0wNzRiLTRiMDktYmQwMS1jZGYyODVlY2NjMDEiLCJpYXQiOjE2NTkzNzA2MjgsImV4cCI6MTY1OTM3NDIyOH0.GKwl3Uactabl6emQy9Ta2R5emGL6IF_v8w85LoR2wAs", "token_type": "bearer", "expires_in": 1659374228, "refresh_token": "not available" }
400 when the payload structure is invalid or the
grant_type
is invalid. Example:{ "message": "The request is invalid.", "modelState": { "grant_type": [ "The grant_type '???' is not supported." ] } }
This is an "ideal" example that provides some consistency with existing messaging. The actual solution can be different based on the package components used in the solution.
401 with no message body when the
client_id
orclient_secret
is invalid. Deliberately not revealing why the authentication attempt failed.
JSON Web Token Response
The access token provided by the /oauth/token endpoint should be in the format of a signed JSON Web Token (JWT). The expected format of the JWT is described in some detail in Meadowlark - Data Authorization. In summary, the token's payload is expected to match this structure:
{ "iss": "ed-fi-meadowlark", "aud": "ed-fi-meadowlark", "sub": "client name", "jti": "3d59b75f-a762-4baa-9116-19c82fdf8de3", "iat": 1636562060, "exp": 3845548881, "client_id": "fbf739c4-fb86-4f03-a477-91af51cc46f2", "roles": [ "vendor" ] }
Token Introspection
An endpoint for verifying a token and accessing information about the token.
Example route signature: POST /oauth/verify with the token to verify in a form-urlencoded body, as well as a valid token authorizing the request itself. Example:
POST /oauth/verify Authorization: bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI.... Content-Type: application/x-www-form-urlencoded token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI....
- The specification notes an optional
token_type
hint. Meadowlark will only support bearer tokens, so this parameter is not necessary. However, the service should not reject a payload that includes this parameter. It can simply ignore the parameter, always validating as a bearer token.
- The specification notes an optional
- responses:
200 if the token is still valid with a message body containing the token information in in a JSON payload.
Active token example:
Active Token{ "active": true, "client_id": "fbf739c4-fb86-4f03-a477-91af51cc46f2", "sub": "client name", "aud": "ed-fi-meadowlark", "iss": "ed-fi-meadowlark", "exp": 1659374285, "iat": 1659374285, "roles": [ "vendor" ] }
Inactive token example:
Inactive Token{ "active": false }
active
will mean that the token is valid:- issued by this application
- not revoked
- not expired
- client has not been deactivated
- 401 if the Authorization header is missing or "corrupt".
- Authorization
- A token with the "vendor" or "host" role can only verify their own token, no others. A token with the "admin" role can verify any token.
- A token with "admin.
One implication of this design is that the meadowlark API application needs to have client credentials with the "admin" role in order to verify incoming tokens.
Client Credential Management
At this time there is no concept of vendors and applications - just keys and secrets. Therefore most of the Admin API Design is not relevant to this project. We simply need to have a route that supports creating keys and secrets.
- Support standard HTTP verbs and status codes
- Requires a token with role claim of "admin", any other valid token gets a 403 "forbidden" response, and invalid or no token gets a 401 response.
- URL endpoint: "oauth/client" to start with, adjust as needed.
- GET
- GET by id
- GET all
POST
body
{ "clientName": "Hometown SIS", "roles": [ "vendor" ] }
- 400 response if clientName is missing and/or there is not at least one role in the request.
Three roles and one variant will be available.
- vendor
- READ access on all descriptors
- Full CRUD access on those resources created by this client credential
- OAuth Token introspection for own token
- host
- Full CRUD access on all Ed-Fi API resources
- OAuth Token introspection for own token
- admin
- Full CRUD on OAuth Client endpoint
- OAuth Token introspection for any token
- assessment
- Disables the reference checks on POST statements
- Generally speaking, one client would have either vendor or host role, not both. However, it is probably not worthwhile to force them to be mutually exclusive through validation.
- vendor
response
location: /oauth/client/a-uuid-v4-value { "active": true "client_id": "a-uuid-v4-value", "client_secret": "a really good random secret", "clientName": "Hometown SIS", "roles": [ "vendor" ] }
- Note the presence of
active
in the output response. That will be an optional value that defaults totrue
on POST.
- Note the presence of
- PUT
body
{ "active": false, "client_id": "a-uuid-v4-value", "clientName": "Hometown SIS", "roles": [ "vendor" ] }
- does not update client_id or client_secret
Generate a new secret for an existing client id:
Request: POST /oauth/client/a-uuid-v4-value/reset
Response
{ "client_id": "a-uuid-v4-value", "client_secret": "a new really good random secret" }
Implementation Notes
Microservice
As of Meadowlark 0.3.0, the code is easily separable, but for ease of use it is still integrated into the same Fastify application.
Application could be separate from the Meadowlark Ed-Fi API, and can be written in either TypeScript or Python.
As a microservice, it will have its own datastore.
- Should support both PostgreSQL and MongoDB
- By default, should use the same database as the Meadowlark API code, but with complete independence from the tables / collections used by the API code.
Can utilize open source* third-party identity provide packages (so as not GPL, LGPL, Affero GPL, or other restrictive / "viral" license)
Bootstrapping Initial Admin Credentials
If there are no admin accounts, relax security to allow new "admin" type client creation without a token. As soon as the first one is created, fully enforce the token authentication, and do not allow a new token to be created.
Table of Contents