Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token Response in token exchange should include issued_token_type #4630

Open
opiation opened this issue Jun 5, 2024 · 1 comment
Open

Token Response in token exchange should include issued_token_type #4630

opiation opened this issue Jun 5, 2024 · 1 comment

Comments

@opiation
Copy link

opiation commented Jun 5, 2024

Medplum's OAuth token endpoint at https://${YOUR_INSTANCE:-api.medplum.com}/oauth/token implements the Token Exchange RFC (or some significant parts of it). According to RFC-, the endpoint's success token exchange should return a token response with a required issued_token_type:

issued_token_type
REQUIRED. An identifier, as described in Section 3, for the representation of the issued security token.

Medplum's server currently does not do this. Based on my interpretation of that description and the resulting access_token I've received in my own local self-hosted instance, I think issued_token_type should be populated and probably with the "urn:ietf:params:oauth:token-type:access_token" value (OAuthTokenType.AccessToken).

Implementation suggestion

Perhaps sendTokenResponse could be amended to attach additional properties to the token response.

@opiation
Copy link
Author

opiation commented Jun 5, 2024

Here are some different approaches with varying degrees of appropriateness:

Add an optional argument to sendTokenResponse

This is an obviously easy solution so it can be very fast to implement and ship but it bubbles up some of the token-building responsibility to the caller which currently only deal with logins and project memberships.

/**
 * Sends a successful token response.
 * @param res - The HTTP response.
 * @param login - The user login.
 * @param membership - The project membership.
 * @param tokenAttrs - Additional properties for the token response
 */
async function sendTokenResponse(res: Response, login: Login, membership: ProjectMembership, tokenAttrs?: Record<string, unknown>): Promise<void> {
  // ...
  
  res.status(200).json({
    token_type: 'Bearer',
    expires_in: 3600,
    scope: login.scope,
    id_token: tokens.idToken,
    access_token: tokens.accessToken,
    refresh_token: tokens.refreshToken,
    project: membership.project,
    profile: membership.profile,
    patient,
    encounter,
    smart_style_url: config.baseUrl + 'fhir/R4/.well-known/smart-styles.json',
    need_patient_banner: !!patient,
    ...tokenAttrs,
    ...fhircastProps, // Spreads no props when FHIRcast scopes not present
  });

Always add the issued_token_type property

This too is a simple fix but does add an unnecessary field to all tokens, affecting every auth network request. This solution relies no the specification instruction that:

The client MUST ignore unrecognized value names in the response.

async function sendTokenResponse(...) {
  // ...

  res.status(200).json({
    token_type: 'Bearer',
    issued_token_type: OAuthTokenType.AccessToken, // Needed for token exchange responses
    expires_in: 3600,
    scope: login.scope,
    id_token: tokens.idToken,
    access_token: tokens.accessToken,
    refresh_token: tokens.refreshToken,
    project: membership.project,
    profile: membership.profile,
    patient,
    encounter,
    smart_style_url: config.baseUrl + 'fhir/R4/.well-known/smart-styles.json',
    need_patient_banner: !!patient,
    ...tokenAttrs,
    ...fhircastProps, // Spreads no props when FHIRcast scopes not present
  });
}

Signal to sendTokenResponse through some argument that the response is for a token exchange request

This feels most desirable because it keeps the token-building responsibility entirely within the sendTokenResponse function without unnecessary payload changes. The caller need only signal to the function that the token response is for an exchange request somehow. This could take the form of some new boolean flag argument, some enum argument or a specific require embedded in the given Response res.

If either of the first 2 options is acceptable, I can submit a pull request with a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

No branches or pull requests

1 participant