Configure service to service authentication with IdP

Configure SSO for a service-to-service implementaton that is protected by an Identity Provider.

Before you begin:
  • You must have an account with an OpenID Connect/OAuth2 provider that will provide authentication services for you.
  • Determine which single sign-on protocol the IdP uses: OpenID Connect or OAuth2. For details, go to Identify the protocol in use by IdP. If using OAuth2, go to Import IdP metadata as OAuth2 for instructions on importing the metadata in the GAS.
About this task:

In service-to-service – also known as "machine-to-machine" – implementation, a web service protected by an Identity Provider (IdP) calls another web service. At runtime, the initial service must authenticate with the IdP using a Client ID and Client Secret. The IdP returns an access token with the scopes (permissions) granting access to the web services. The access token is available via the GAS environment at runtime, and the initial web service will send this access token to any other service it needs to use.

Typically, you provide a web application secured by your IdP to provide the interface to the web services, and the services are protected by the application. But in this instance, clients may create their own applications to access your web services; therefore, you want to protect your web service directly with the IdP, which in turn will protect other services it accesses. This is the implementation of service-to-service described in this task.

Steps

  1. Configure your web service xcf file for delegation.
    In the DELEGATE element, set the service attribute to the Genero GeneroAccessService, and provide the IdP URL in the IDP element. GeneroAccessService is the REST web service in $FGLDIR that handles delegation in the service calls, and forwards the access token if needed in a call to another service.
    <APPLICATION Parent="ws.default" ...>
      <EXECUTION>
        </ENVIRONMENT_VARIABLE>
        </PATH>
        </MODULE>
        <DELEGATE service="services/GeneroAccessService">
           <IDP>my_OAuth_provider_url</IDP>
        </DELEGATE>
       #...
      </EXECUTION>
    </APPLICATION>
  2. Register your web service as a "service-to-service" on the IdP site.

    For more information on registering a web service, refer to the IdP documentation. Typically, it consists of the following tasks:

    1. Register the details of the web service with the IdP.
    2. Register all the scopes (permissions) that might be granted to clients. These are the scopes you should already have defined in your web service 4GL with the WSScope attribute to provide security globally for the service (on the WSInfo record) or for its functions. You should have a list of such WSScope entries.

    The IdP provides the CLIENT_ID and SECRET_ID that you use for requesting access tokens to the protected web services.
  3. Generate stub files with code to handle OAuth access for other Genero web service you need.

    Any other web service you call from the "service-to-service" web service must be able to retrieve the access token that is forwarded to it, and its stub file must be generated to handle OAuth as shown in these steps.

    1. Code to retrieve the access token.
      You need to call the OAuthAPI.InitService function to initiate OAuth and register the access token with the GWS engine.
      Sample code to do this is shown in the myInitServiceCall function. In a REST function, the access token – provided via the GAS environment at runtime – can be retrieved via the WSContext variable, ctx in the example.
      # Other web service module
      IMPORT FGL OAuthAPI
      
      PRIVATE DEFINE ctx DICTIONARY ATTRIBUTE(WSContext) OF STRING
      
      PUBLIC FUNCTION myInitServiceCall()
          DEFINE access_token STRING
      
          # retrieve access_token
          LET access_token=ctx["OIDC_ACCESS_TOKEN"]
      
          # Init OAuth service
          IF NOT OAuthAPI.InitService(5, access_token) THEN
              RETURN "Cannot initiate OAuth service"
              EXIT PROGRAM 1
          END IF
      
      END FUNCTION
      # web service functions
    2. Use the fglrestful tool with the --oauth yes option to generate the stub.
      (Line breaks have been added to improve readability.)
      fglrestful -o ws_client 
          --oauth yes 
          http://myhost:6394/gas/ws/r/myGroup/myXcf/myClientService?openapi.json
      Where:
      • ws_client specifies the filename of the stub in the output (-o) option.
      • --oauth yes specifies that the web service is secured and code to handle OAuth access is generated in the stub, taking the access token into account.
      • ?openapi.json query string gets the OpenAPI document.

      The ws_client.4gl is generated.

    3. Compile ws_client.4gl.
      The stub file must be imported into your "service-to-service" web service, as shown in the next step.
  4. Code in your "service-to-service" web service to get an access token.
    You must call the OAuthAPI.RetrieveServiceToken function using the CLIENT_ID and SECRET_ID credentials of the service to request an access token. You must provide the credentials when calling this function.
    # Web service (registered as a "service-to-service")
    
    IMPORT FGL ws_client # import OAuth-ready client service
    IMPORT FGL OAuthAPI
    
    DEFINE metadata OAuthAPI.OpenIDMetadataType
    DEFINE token  STRING
    DEFINE expire INTEGER
    DEFINE client_id STRING
    DEFINE secret_id STRING
    DEFINE scope STRING
    DEFINE idp_url STRING
    
    PUBLIC FUNCTION myGetServiceToken()
        TRY
            # Retrieve the access token
            CALL OAuthAPI.FetchOpenIDMetadata(20, idp_url)
                RETURNING metadata.*
            IF metadata.issuer IS NULL THEN
                ERROR "IdP not available"
                EXIT PROGRAM 1
            ELSE
                CALL OAuthAPI.RetrieveServiceToken(5, metadata.token_endpoint, client_id, secret_id, scope )
                RETURNING token, expire
                IF token IS NULL THEN
                DISPLAY "Unable to retrieve token"
                EXIT PROGRAM 1
                ELSE
                    DISPLAY "Access token value :",token
                    DISPLAY SFMT("Token expires in %1 seconds",expire)
                END IF
            END IF
        CATCH
            DISPLAY "ERROR : ",status,sqlca.sqlerrm
            EXIT PROGRAM 1
        END TRY
    
    END FUNCTION
    # web service functions
Troubleshooting access
  • If errors are encountered accessing your service, check the logs: vm-services-GeneroAccessService*.log, and the AccessService.log:
    For instance, if you get error 403 "Forbidden" and you see the following message in the AccessService.log:
    ERROR : 29389 - [AccessService] "RetrieveIDPMetadata" INSERT failed :UNIQUE constraint failed: fjs_provider.issuer
    Check that the IdP's URL in the DELEGATE element of your web service xcf is correct.
  • If a call made to a Genero web service function fails, check the error status code returned to the client. For example, a value of -1 indicates a failed operation. The code -15553 with a description such as Asynchronous Connection failed or connection timeout expired indicates an infrastructure error.

    Check that you have a call to OAuthAPI.InitService() in your web service.

    For more information on handling errors, refer to the Handle REST server errors page in Genero Business Development Language User Guide