Class OutboundSmartClient

java.lang.Object
ca.cdr.test.app.clients.OutboundSmartClient

public class OutboundSmartClient extends Object
A client for performing SMART on FHIR OAuth 2.0 authorization flows.

This client provides methods to interact with SMART on FHIR OAuth 2.0 endpoints and supports the following OAuth 2.0 flows:

  • Authorization Code Flow - For web applications requiring user consent

The client handles HTTP communication with the SMART authorization server, form-based authentication, CSRF token management, and token exchange operations.

Thread Safety:

This client is not thread-safe. Each thread should use its own instance or external synchronization should be applied.

Author:
Claude Sonnet 4
See Also:
  • Constructor Details

    • OutboundSmartClient

      public OutboundSmartClient(@Nonnull org.springframework.web.client.RestClient theRestClient, @Nonnull String theSmartRootUrl)
      Creates a new OutboundSmartClient instance.
      Parameters:
      theRestClient - the REST client to use for HTTP communication with the SMART server. This client should be configured with appropriate timeouts, SSL settings, and cookie management as needed for the target SMART server.
      theSmartRootUrl - the root URL of the SMART authorization server (e.g., "https://auth.example.com"). This should not include trailing slashes or specific endpoints.
      Throws:
      NullPointerException - if either parameter is null
  • Method Details

    • exchangeCodeWithSecret

      public String exchangeCodeWithSecret(String theClientId, String theClientSecret, String theCode, String theRedirectUri, boolean secret_as_param) throws IOException
      Exchanges an authorization code for an access token with flexible client authentication.

      This method provides full control over how the client secret is transmitted to the authorization server. It supports both HTTP Basic authentication (in the Authorization header) and form parameter authentication (in the request body).

      The method constructs a form-encoded POST request to the token endpoint with the authorization code and other required parameters. The response is parsed to extract the access token.

      Parameters:
      theClientId - the OAuth 2.0 client identifier. Must not be null.
      theClientSecret - the client secret. If null, no client authentication is performed.
      theCode - the authorization code to exchange. Must not be null.
      secret_as_param - if true, the client secret is sent as a form parameter (client_secret); if false, the client secret is sent via HTTP Basic authentication in the Authorization header. This parameter is ignored if theClientSecret is null.
      Returns:
      the access token as a string
      Throws:
      IOException - if the token exchange request fails or if the response cannot be parsed
      See Also:
    • refreshToken

      @Nonnull public String refreshToken(@Nonnull String theClientId, @Nullable String theClientSecret, @Nonnull String theRefreshToken)
      Refreshes an access token using a refresh token.

      When an access token expires, a refresh token (if available) can be used to obtain a new access token without requiring the user to re-authorize the application. This method performs the token refresh operation.

      The authorization server may issue a new refresh token along with the new access token, and the old refresh token should be considered invalid.

      Note: This method is currently not implemented and will throw an UnsupportedOperationException.

      Parameters:
      theClientId - the OAuth 2.0 client identifier. Must match the original client that obtained the refresh token. Must not be null or empty.
      theClientSecret - the OAuth 2.0 client secret. Required for confidential clients. May be null for public clients, depending on server configuration.
      theRefreshToken - the refresh token obtained from a previous token request. Must not be null or empty.
      Returns:
      JSON document as a string containing the new access token and potentially a new refresh token. Typical response includes: access_token, token_type, expires_in, refresh_token (optional), scope
      Throws:
      UnsupportedOperationException - always, as this method is not yet implemented
      IllegalArgumentException - if required parameters are null or empty
      See Also:
    • performAuthorizationCodeFlow

      public String performAuthorizationCodeFlow(String clientId, String redirectUri, String[] theScopes, String state, String theSecret, String theUsername, String thePassword) throws IOException
      Performs the complete OAuth 2.0 authorization code flow including user login.

      This method orchestrates the entire authorization code flow by:

      1. Obtaining an authorization code through the authorization endpoint
      2. Exchanging the authorization code for an access token

      The method handles both confidential clients (with secret) and public clients (without secret).

      Parameters:
      clientId - the OAuth 2.0 client identifier. Must not be null.
      redirectUri - the redirect URI for receiving the authorization code. Must not be null.
      theScopes - array of requested OAuth 2.0 scopes. Must not be null.
      state - the state parameter for CSRF protection. May be null.
      theSecret - the client secret. If null, the client is treated as public.
      Returns:
      the access token as a string
      Throws:
      IOException - if any HTTP communication or response parsing fails
      See Also:
    • authorizeBeforeLogin

      public String authorizeBeforeLogin(String theRequestUrl) throws IOException
      Attempts to access an authorization URL and handles the redirect to the login screen.

      This method makes a GET request to the authorization endpoint and expects to receive a redirect response (302/303) that points to the login screen. This is part of the normal OAuth 2.0 flow when the user is not yet authenticated.

      Parameters:
      theRequestUrl - the authorization URL to request. Must not be null.
      Returns:
      the login screen URL extracted from the Location header of the redirect response
      Throws:
      IOException - if the request URL doesn't redirect to the expected signin location
      org.springframework.web.client.RestClientException - if the response is not a redirect (302/303) or if the Location header is missing
    • getAuthorizationCode

      public String getAuthorizationCode(String clientId, String redirectUri, String[] theScopes, String state, String theUsername, String thePassword) throws IOException
      Obtains an authorization code through the OAuth 2.0 authorization flow.

      This method performs the first part of the authorization code flow:

      1. Constructs the authorization URL with the provided parameters
      2. Follows redirects to the login screen
      3. Performs login
      4. Completes the authorization and extracts the authorization code

      The authorization code can then be exchanged for tokens using exchangeCode(String, String,String) or related methods.

      Parameters:
      clientId - the OAuth 2.0 client identifier. Must not be null.
      redirectUri - the redirect URI for receiving the authorization code. Must not be null.
      theScopes - array of requested OAuth 2.0 scopes. Must not be null.
      state - the state parameter for CSRF protection. May be null.
      Returns:
      the authorization code as a string
      Throws:
      IOException - if any HTTP communication fails or if the authorization code cannot be extracted
      See Also:
    • buildScopeString

      public String buildScopeString(String[] theScopes)
      Builds a space-separated scope string from an array of scopes.

      This utility method converts an array of OAuth 2.0 scope strings into a single space-separated string as required by the OAuth 2.0 specification.

      Parameters:
      theScopes - array of OAuth 2.0 scopes. Must not be null.
      Returns:
      space-separated scope string, or empty string if no scopes provided
    • exchangeCode

      public String exchangeCode(String theClientId, String theCode, String theRedirectUri) throws IOException
      Exchanges an authorization code for an access token (public client).

      This is a convenience method for public clients that don't have a client secret. It delegates to exchangeCodeWithSecret(String, String, String, String) with a null client secret.

      Parameters:
      theClientId - the OAuth 2.0 client identifier. Must not be null.
      theCode - the authorization code to exchange. Must not be null.
      Returns:
      the access token as a string
      Throws:
      IOException - if the token exchange request fails
      See Also:
    • exchangeCodeWithSecret

      public String exchangeCodeWithSecret(String theClientId, String theClientSecret, String theCode, String theRedirectUri) throws IOException
      Exchanges an authorization code for an access token using HTTP Basic authentication.

      This is a convenience method that delegates to exchangeCodeWithSecret(String, String, String, String, boolean) with the secret_as_param flag set to false, meaning the client secret will be sent in the Authorization header using HTTP Basic authentication.

      Parameters:
      theClientId - the OAuth 2.0 client identifier. Must not be null.
      theClientSecret - the client secret. If null, no authentication is performed.
      theCode - the authorization code to exchange. Must not be null.
      Returns:
      the access token as a string
      Throws:
      IOException - if the token exchange request fails
      See Also:
    • authorizeAfterLogin

      public String authorizeAfterLogin(String theAuthorizeUrl) throws IOException
      Completes the authorization step and extracts the authorization code from the redirect.

      This method makes a request to the authorization URL (after user login) and expects to receive a redirect response containing the authorization code. The authorization code is extracted from the callback URL in the Location header.

      Parameters:
      theAuthorizeUrl - the authorization URL to request (typically received after login). Must not be null.
      Returns:
      the authorization code extracted from the redirect URL
      Throws:
      IOException - if the authorization code cannot be found in the redirect URL
      org.springframework.web.client.RestClientException - if the response is not a redirect (302/303) or if the Location header is missing
      See Also:
    • extractCodeFromUrl

      public static String extractCodeFromUrl(String theUrl) throws IOException
      Extracts the authorization code from a callback URL.

      This utility method parses the authorization code from the "code" query parameter in an OAuth 2.0 callback URL. This is typically used when processing the redirect response from the authorization server.

      Parameters:
      theUrl - the callback URL containing the authorization code parameter. Must not be null.
      Returns:
      the authorization code value
      Throws:
      IOException - if the authorization code parameter is not found in the URL
    • loginWithPassword

      public String loginWithPassword(String theUser, String thePassword) throws IOException
      Performs form-based login with username and password credentials.

      This method handles the complete login process:

      1. Fetches the login page and extracts the CSRF token
      2. Submits the login form with credentials and CSRF token
      3. Follows the redirect response to get the next URL in the flow
      Parameters:
      theUser - the username for authentication. Must not be null.
      thePassword - the password for authentication. Must not be null.
      Returns:
      the URL to redirect to after successful login (typically the authorization endpoint)
      Throws:
      IOException - if the login request fails or if the expected redirect response is not received
      org.springframework.web.client.RestClientException - if the HTTP response status is not a redirect (302/303)