RESTHost Guide - Securing your API

Authentication

The below sections cover the implementation of the following Open API security schemes in RESTHost svc

The explanations and sample are mostly for Linx5 users. We`ve included samples both in Linx 5 and Linx 6

Feel free to contact support@linx.software and we'll assist.

:information_source: Note: You are able to implement additional custom authentication in the OperationEvent_BeforeOperation EVT, find out more here.

:floppy_disk: Sample: Take a look at the this sample Solution to help in understanding of secuirty implementation.

Please note the terms 'Process' and 'Custom Type' have been depreciated and have been replaced with 'Function' and 'Type' respectively. More details here.

Basic Authentication

The following sections describe how to secure your RESTHost svc operations by using the HTTP Basic security scheme and custom authentication logic.

Applying security

In order to apply or attach an authentication scheme to an operation, you must first add the security scheme globally to the API definition, and then apply it to specific operations.

In the API definition, in the securitySchemes section, include the following “BasicAuth” security scheme definition (this is the arbitrary name you give the scheme):

    components:                 # Securirty schemes available to apply.
      securitySchemes:
          BasicAuth:            # Arbitary name for security scheme.
              type: http        # Can be one of 'http' or 'apiKey'.
              scheme: basic     # Type of HTTP scheme

Then, apply the security scheme to your chosen operation by adding “BasicAuth” (arbitrary name you gave the scheme) as a security scheme to the path in the API Definition:

  /helloworld:
    get:
      summary: Return the text 'Hello World!'
      description: Return the text 'Hello World!'
      operationId: HelloWorld
      security:
       - BasicAuth: []
      responses:
        '200':
          description: OK (200)
          content:
            application/json:
              schema:
                type: string

Implementing custom authentication

In the above example, operation HelloWorld, will now have the HTTP Basic security scheme named BasicAuth attached to it.

This will mean that when a request is made to the /helloworld endpoint:

  1. The built-in security protocols validate the format of the Basic Authentication credentials in the form of the Authorization header containing the value Basic base64encoded(username:password).

    "Key":"Authorization",
                  "Value": [
                            "Basic YWRtaW46YWRtaW4="
                           ]
    
    
  2. The OperationEvents _AuthenticateEVT event is then executed which contains custom logic to validate the credentials (described below).

Linx implicitly handles the verification of the format of the credentials, that is to say that a check is done that the credentials are submitted in the Authorization header. However, custom logic still needs to be added in the OperationEvents_Authenticate EVT in order to extract, decode and validate the credentials according to custom rules.

Parsing Headers

In order to validate these credentials in OperationEvents_Authenticate EVT, you need to parse the $.Input.Data.HTTPContext for the incoming headers for this Authorization header.

    "Headers": [{
                  "Key":"Authorization",
                  "Value": [
                            "Basic YWRtaW46YWRtaW4="
                           ]
                }]  

To parse the incoming headers, you are able to take 2 approaches:

  • Simple: Extract the value by using Linx functions to create a looping , decision structure.

  • Complex: Extract the value in a single expression making use of linq.

Simple

The 'simple' approach can be used by users who are not comfortable using complex linq expressions.

Linx Designer view - Authenticate Event - Custom Basic Authentication

image19

Process breakdown:

Described below is a custom logic flow which will result in the HTTP Basic encoded credentials being extracted from the HTTP Context Headers. (Note: This is for demonstration purposes only and your exact approach may differ).

  • IfElse_CheckAuthenticationType : An IfElse FNC performs a check on the AuthenticationData SchemeType.

  • Basic : If the SchemeType == BasicAuth:

    • ForEach_LoopHeaders: A ForEach FNC performs a loop through the HTTPContext.Headers list:

      • IfElse_CheckForHeaderAuthorizationKey : An IfElse FNC performs a check on the current loop header Key value.

      • Header_Key_ContainsAuthorization : If the current loop header Key value contains the string Authorization .

        • ForEach_LoopValueSubList : A ForEach FNC performs a loop through the ForEach_LoopHeaders.Loop.Value list. This means that the inner Value list of that particular header item is being looped through.

        • IfElse_CheckForBasicValue: An IfElse FNC performs a check on the current loop Value item value.

          • Header_Value_ListItemContainsBasic: If the current loop Value item starts with the string 'Basic ':

          • BasicCredentials_base64encoded: A STRTYP is set to the value of the current loop Value item with the text 'Basic ' replaced.

The above logic will result in the base64 encoded credentials being extracted from the Authorization header.

In order to validate the credentials, you will need to decode them and validate the according to custom rules which is described further below.

Complex

You can achieve the exact same result as the above process Function using a single Linx expression involving linq operators.

$.Input.Data.HttpContext.Headers.SelectMany(headers => headers.Value).Where(item => item.StartsWith("Basic ")).First().Replace("Basic ","")

Expression breakdown:


  1. The LST TYP of all headers are returned.

    $.Input.Data.HttpContext.Headers
    


  1. All the items contained in the Value LST TYPof all the Headers.
     .SelectMany(headers => headers.Value) 
    

  1. The LST TYP of values for each header is then filtered for items that start with the text "Basic ".
     .Where(item => item.StartsWith("Basic"))
    

  1. The first item in the resulting LST TYP is then extracted.
     .First()
    

  1. Finally, the text "Basic " is removed.
     .Replace("Basic ","")
    

The above expression logic will result in the encoded credentials being extracted from the Authorization header into a STR TYP.

In order to validate the credentials, you will need to decode and validate them according to custom rules which are described further in this section.

Decode credentials

Basic authentication credentials in the Authorization header are received the form of:

base64encoded(username:password)

This base64 string then needs to be decoded into a UTF-8 STR TYP so that you are able to extract the username and password fields respectively.

Once you have parsed the headers for the Basic Authorization credentials you now have to decode them from base64 so that you are able to interact with the specific credentials.

The resulting STR TYP will be in the format username:password

Decoding a base64 STR TYP in Linx involves converting the base64 STR TYP into a LST<BYT> TYP format and then converting it into UTF-8 format.

The above can be achieved with the following expression:

BasicCredentials_base64encoded.ToBytesFromBase64().ToString("UTF8")

  1. First, the extracted base64 encoded STR TYP is set as the base of the expression.

     BasicCredentials_base64encoded
    

    image25

    *The : BasicCredentials_base64encoded above is a STR TYP that holds the value of the extracted credentials from the step above.


  1. The base64 encoded STR TYP is then converted into a list of bytes ( LST<BYT> TYP ) using the built in expression:
     .ToBytesFromBase64()
    
    image26

  1. The LST<BYT> TYP is then converted into a STR TYP in the UTF-8 format:

     .ToString("UTF-8")
    

    image27

    The result of the above example is:

    demo@linx.software:admin
    

Now what would typically happen is that you would then extract the username and password by using expressions to to split the string according to the ':' character, which can be done with the following expressions:


  1. To extract the username or first credential:

     BasicCredentials_base64decoded.Substring(0,BasicCredentials_base64decoded.IndexOf(":"))
    

    A .Substring() method is used which extracts the characters from the start of the base64 decoded STR TYP up until the position of the ':' character

    image28


  1. To extract the password or second credential:

        BasicCredentials_base64decoded.Substring(BasicCredentials_base64decoded.IndexOf(":") + 1)
    

    A .Substring() method is used which extracts the characters from the position of the ':' character until the end of the STR TYP.

    image29


Validate credentials

Now that the credentials have been extracted into their username and password fields, what would typically happen now is that custom logic would need to be added to OperationEvents_AuthenticateEVT in order to validate the credentials in some way.

A typical example would be validating the hashed password against the username from a data source such as a database using an ExecuteSQL FNC .

In the below example, the extracted password is first hashed using a GenerateHash FNC and then compared with the stored hashed password of the user matching the username in the database using an ExecuteSQL FNC.

image

  1. The username field is extracted and decoded into a STR TYP.
  2. The password field is extracted and decoded into a STR TYP.
  3. The ExecuteSQL FNC performs a search on the database and returns an id which matches the credentials, if no id is found then a 0 is returned.
  4. A check is then done using IfElse FNC to see if the id is greater than 0 which results in a Y/NTYP.
  5. If the condition is met, the IsAuthenticated result as well as additional user information of the OperationEvents_AuthenticateEVT is set using a SetValue FNC

:green_book: Learn more abouting setting the output of this event.


API Key

Applying security

In order to apply an authentication scheme to an operation, you must first add the security scheme globally to the API definition, and then apply it to specific operations.

In your API definition include the following “ApiKeyAuth” security scheme definition (*this is the arbitrary name you give the scheme):

    components:                 # Security schemes available to apply to operations.
      securitySchemes:
          ApiKeyAuth:           # Arbitary name for security scheme.
              type: apiKey      # Can be one of 'http' or 'apiKey'.
              in: header        # Location - can be header,query,cookie
              name: X-API-Key   # Name of Header

Then, apply the security scheme to your chosen operation by adding “ApiKeyAuth” (*this is the arbitrary name you gave the scheme) as a security scheme to the path:

paths:
  /helloworld:
    get:
      summary: Return the text 'Hello World!'
      description: Return the text 'Hello World!'
      operationId: HelloWorld
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: OK (200)
          content:
            application/json:
              schema:
                type: string

Implementing custom authentication

In the above example, the operation HelloWorld, will now have an API Key security scheme attached to it.

This will mean that when a request is made to the /helloworld endpoint:

  1. The built-in security protocols validate the format of the API Key Authentication credentials in the form of the header matching the API Key from the definition.
  2. The OperationEvents_AuthenticateEVT is then executed which contains custom logic to validate the API Key (described below).

Although the API Key security scheme is applied to an operation, custom logic still needs to be added in the OperationEvents_AuthenticateEVT in order to validate the API Key according to custom rules.

In the OperationEvents_AuthenticateEVT, the incoming request structure will include an AuthenticationData object as an input.

Contained in the AuthenticationData is the API Key object.

image

The Input.Data.AuthenticationData.ApiKey will contain the following:

  • ProvidedToken: API Key value submitted
  • TokenLocation: Location of API Key in request (header, query string, cookie)
  • TokenName: Name of API Key authentication scheme from the API definition.

In order to validate the ProvidedToken, you will need to build custom logic.

In the example below a database is queried using ExecuteSQL FNC for a record matching the API Key submitted in the request. The ExecuteSQL FNC returns the id and some other fields of the user that matches the ProvidedToken.

The result of the event ($.Output.Data.HttpContext.User.IsAuthenticated) is set along with additional user information.


Linx Designer View


Setting the Authenticated result

For HTTP Basic and API Key security schemes, the OperationEvents_AuthenticateEVT will execute before any events or operations. The $.Output.Data.HttpContext.User.IsAuthenticated of this event will affect the request flow. If this is False then a (401) Unauthorized response is returned, if True then the request flow will continue.

To set the result of the OperationEvents_AuthenticateEVT , you would add a SetValue FNC to OperationEvents_AuthenticateEVT and set Target as the whole $.Output.Data object, then use the image32 to set the $.Output.Data.HttpContext.User.IsAuthenticated field to the result of the validation (Y/NTYP), along with any other HTTPContext values that you want to assign.

If the value of the $.Output.Data.HtttpContext.User.IsAuthenticated is not explicitly set, the it will be defaulted to False, in the case the request will fail with a (401) Unauthorized response and the request flow will cease.

In the below example the $.Output.Data is set as the Target of a SetValue FNC . Then, using the image32 editor, the HttpContext object is expanded and then the HttpContext.User fields are then set as the source.

This allows additional information to be passed in with the request. In this case the id of the authenticated user returned from ExecuteSQL FNC will be passed in the $.Output.Data.HttpContext.User.Name field which can be used in the subsequent operation without having to re-extract this information.


Linx Designer View


Bearer

The HTTP Bearer authentication scheme involves the issuing and receiving of cryptographic 'tokens' which contain authentication information. The advantage of this scheme is that all the verification details are contained in the token itself.

For this demonstration, a RESTHost svc operation will be secured by JWT Tokens. These are digitately signed self-contained verification objects with are used to authenticate requests. Linx has functionality which allows in the generation and verification of these tokens.

Applying security

In order to apply an authentication scheme to an operation, you must first add the security scheme globally to the API definition, and then apply it to specific operations.

In your API definition include the following “Token” (this is the arbitrary name you give the scheme) security scheme definition:

    components:                   # Security schemes available to apply to operations.
      securitySchemes:
          Token:                  # Arbitary name for security scheme.
              type: http          # Can be one of 'http' or 'apiKey'.
              scheme: bearer      # Can be 'basic' or 'bearer'
              bearerFormat: JWT   # Documentation purposes

Then, apply the security scheme to your chosen operation by adding “Token” (this is the arbitrary name you gave the scheme) as a security scheme to the path:

  /helloworld:
    get:
      summary: Return the text 'Hello World!'
      description: Return the text 'Hello World!'
      operationId: HelloWorld
      security:
      - Token: []
      responses:
        '200':
          description: OK (200)
          content:
            application/json:
              schema:
                type: string

Authentication Configuration

JWT Tokens require a secret in order to cryptographically sign them.

In order for Linx to automatically handle the verification of the token, you need to configure the Linx RESTHost svc Auth config to reference this secret (this value must be the same as the one used to generate the token initially).

Once you've imported your API definition, Linx will automatically import the authentication configurations needed.

image45

However, you still need to reference the secret signing key.


Configure signing key:

  1. Add a new $.Setting value to your Solution, this will contain the key used to sign the JWT Token.

  2. Expand the Auth config property of the RESTHost svc using the field editor.

  3. Reference the new $.Setting value you just created as the Value field of the Secret key and save.


Linx Designer View


In the above example, the HelloWorld operation , will now have the Token security scheme attached to it.

This will mean that when a request is made to the /helloworld endpoint:

  1. The built-in security protocols will automatically verify the JWT Token using the signing key. If successful the request will proceed, if the token is invalid then a (401) Unauthorized error response will be returned.

There is no need to create custom authentication logic to verify the token as it is handled by Linx internally based on the configured secret.

However, if you would like to verify the token explicitly and extract the payload, you can do so by following the below sections:

:green_book: Learn more about generating JWT Tokens.

:green_book: Learn more about implementing custom logic to verify JWT tokens.

2 Likes