import ballerina/http;
import ballerina/io;
import ballerina/auth;
endpoint endpoints:ApiEndpoint ep {
    port:9090,
    secureSocket:
    {
        keyStore:{
          filePath:"${ballerina.home}/bre/security/ballerinaKeystore.p12",
          password:"ballerina"
        },
        trustStore:{
          filePath:"${ballerina.home}/bre/security/ballerinaTruststore.p12",
          password:"ballerina"
       }
    }
};@http:ServiceConfig {
    basePath:"/hello"
}
@auth:Config {
    authentication:{enabled:true},
    scopes:["xxx"]
}
service<http:Service> echo bind ep {
    @http:ResourceConfig {
        methods:["GET"],
        path:"/sayHello"
    }
    @auth:Config {
        scopes:["scope2"]
    }
    hello (endpoint client, http:Request req) {
        http:Response res = {};
        res.setStringPayload("Hello, World!!!");
        _ = client -> respond(res);
    }
}
usage () {
    echo "Usage: bash userstore-generator.sh -u {username} -p {password} -g {comma separated groups} " 1>&2;
    exit 1;
}
generateUid () {
    echo -n ${1} | shasum -a 1 | awk '{print $1}'
}
generateHash () {
    echo -n ${1} | shasum -a 256 | awk '{print toupper($1)}'
}
writeToFile () {
    echo "# start of a user section" >> ${1}
    echo "[${2}]" >> ${1}
    echo "userid=\"${3}\"" >> ${1}
    echo "[${3}]" >> ${1}
    echo "password=\"${4}\"" >> ${1}
    if [[ ! -z ${5} ]]; then
        echo "groups=\"${5}\"" >> ${1}
    fi
    echo "# end of a user section" >> ${1}
    echo "" >> ${1}
    echo "userstore updated successfully with details of user: ${2}"
}
while getopts "u:p:g:" input; do
    case "${input}" in
        u)
            username=${OPTARG}
            ;;
        p)
            password=${OPTARG}
            ;;
        g)
            groups=${OPTARG}
            ;;
        *)
            usage
            ;;
    esac
done
if [ -z "${username}" ] || [ -z "${password}" ]; then
    usage
fi
writeToFile "ballerina.conf" "${username}" "$(generateUid ${username})" "$(generateHash ${password})" "${groups}"

Secured Hello World Service with Basic Auth

A service can be secured using basic authentication and optionally, authorization. The userstore used is a simple file based one. The file based userstore stores usernames, password hashes for authentication purposes, as well as groups and scopes for authorization purposes. Ballerina uses the concept of scopes for authorization. A resource declared in a Service can be bound to a scope. A scope is mapped to one or more groups. To access the resource, a user need to be in the same groups to which the corresponding scope is mapped.

import ballerina/http;
import ballerina/io;
import ballerina/auth;
endpoint endpoints:ApiEndpoint ep {
    port:9090,

The endpoint used here is ‘endpoints:ApiEndpoint’, which by default tries to authenticate and authorize each request. The developer has the option to override the authentication and authorization at service and resource level.

    secureSocket:
    {
        keyStore:{
          filePath:"${ballerina.home}/bre/security/ballerinaKeystore.p12",
          password:"ballerina"
        },
        trustStore:{
          filePath:"${ballerina.home}/bre/security/ballerinaTruststore.p12",
          password:"ballerina"
       }
    }
};

The secure hello world sample uses https.

@http:ServiceConfig {
    basePath:"/hello"
}
@auth:Config {
    authentication:{enabled:true},
    scopes:["xxx"]
}
service<http:Service> echo bind ep {
    @http:ResourceConfig {
        methods:["GET"],
        path:"/sayHello"
    }

Auth configuration comprises of two parts - authentication and authorization. Authentication can be enabled by setting ‘authentication:{enabled:true}’ annotation attribute. Authorization is based on scopes, where a scope maps to one or more groups. For a user to access a resource, the user should be in the same groups as the scope. To specify one or more scope of a resource, the annotation attribute ‘scopes’ can be used.

    @auth:Config {
        scopes:["scope2"]
    }
    hello (endpoint client, http:Request req) {
        http:Response res = {};
        res.setStringPayload("Hello, World!!!");
        _ = client -> respond(res);
    }
}

The authentication and authorization settings can be overridden at resource level. The hello resource would inherit the authentication:{enabled:true} flag from the service level, and override scope defined in service level (xxx) with scope2.

To perform authentication and authorization, credentials and related data is stored in a simple file based userstore. The ballerina.conf file is used for this purpose.

To generate the file based userstore and insert relevant data, two sample bash scripts are used. These scripts will add user credentials, create the mapping between the user & groups and create the mapping between the scopes & the groups.

usage () {
    echo "Usage: bash userstore-generator.sh -u {username} -p {password} -g {comma separated groups} " 1>&2;
    exit 1;
}
generateUid () {
    echo -n ${1} | shasum -a 1 | awk '{print $1}'
}
generateHash () {
    echo -n ${1} | shasum -a 256 | awk '{print toupper($1)}'
}
writeToFile () {
    echo "# start of a user section" >> ${1}
    echo "[${2}]" >> ${1}
    echo "userid=\"${3}\"" >> ${1}
    echo "[${3}]" >> ${1}
    echo "password=\"${4}\"" >> ${1}
    if [[ ! -z ${5} ]]; then
        echo "groups=\"${5}\"" >> ${1}
    fi
    echo "# end of a user section" >> ${1}
    echo "" >> ${1}
    echo "userstore updated successfully with details of user: ${2}"
}
while getopts "u:p:g:" input; do
    case "${input}" in
        u)
            username=${OPTARG}
            ;;
        p)
            password=${OPTARG}
            ;;
        g)
            groups=${OPTARG}
            ;;
        *)
            usage
            ;;
    esac
done
if [ -z "${username}" ] || [ -z "${password}" ]; then
    usage
fi
writeToFile "ballerina.conf" "${username}" "$(generateUid ${username})" "$(generateHash ${password})" "${groups}"

To generate the userstore with user credentials, the following bash script can be used.

$ bash userstore-generator.sh -u {username} -p {password} -g {comma separated groups}

To use the script, copy the content to a file named “userstore-generator.sh” and execute it with the relevant parameters.

usage () {
    echo "Usage: bash permissionstore-generator.sh -s {scope name} -g {comma separated groups} " 1>&2;
    exit 1;
}
writeToFile () {
    echo "# start of a permission section" >> ${1}
    echo "[${2}]" >> ${1}
    echo "groups=\"${3}\"" >> ${1}
    echo "# end of a permission section" >> ${1}
    echo "" >> ${1}
    echo "permissionstore updated successfully with details of scope: ${2}"
}
while getopts "s:g:" input; do
    case "${input}" in
        s)
            scope=${OPTARG}
            ;;
        g)
            groups=${OPTARG}
            ;;
        *)
            usage
            ;;
    esac
done
if [ -z "${scope}" ] || [ -z "${groups}" ]; then
    usage
fi
writeToFile "ballerina.conf" "${scope}" "${groups}"

To create the mapping between scopes and groups, the following bash script can be used.

$ bash permissionstore-generator.sh -s {scope name} -g {comma separated groups}

To use the script, copy the content to a file named “permissionstore-generator.sh” and execute it with the relevant parameters.

$ ballerina run secured-hello-world-service-with-basic-auth.bal
ballerina: initiating service(s) in 'secured-hello-world-service-with-basic-auth.bal'
ballerina: started HTTPS/WSS endpoint 0.0.0.0:9090

To start the service, put the code in “secured-hello-world-service-with-basic-auth.bal” and use “ballerina run” command. Make sure the ballerina.conf file populated by the scripts above is present in the same directory as the secured-hello-world-service-with-basic-auth.bal file.

$ ballerina build secured-hello-world-service-with-basic-auth.bal
$ ls
secured-hello-world-service-with-basic-auth.balx	secured-hello-world-service-with-basic-auth.bal

To build a compiled program file, we can use the “ballerina build” command followed by the service package.

$ curl -vk -H "Authorization: Basic <basic authentication header>" https://localhost:9090/hello/sayHello
Hello, World!

Invoke the service using “curl”. Note that its required to provide the correct basic authentication header with the curl command.