Skip to content

Develop a client

Gawen Arab edited this page Sep 19, 2019 · 1 revision

How to request data

This guide explains how to implement an Alias client to fetch user's data.

Create a contract

Manually

First, you'll need to generate a contract which describes who you are, what data you want to access and why. A contract is built as follow:

const Anychain = require('@alias/anychain');
const chain = new Anychain();

// generate a secret key
const secretSeed = chain.seed();

// derive signature key
const signSk = chain.signSeedKeypair(chain.seedOf(secretSeed, 32, "sign"));

// generate a client declaration
const client = {
    type: "alias.client.decl",

    // Application's name
    name: "Example App",

    // Application's description
    desc: "First example Alias client",

    // Application's Web domain
    domain: "client.alias",

    // Company legally responsible for the application
    company: "FooBar Inc",
};

client.crypto = { sign: signSk.publicKey };
const signedClient = chain.sign(signSk, client);

const contract = {
    type: "alias.contract",

    // link to the signed client declaration
    client: signedClient,

    // list of legal bases: 'contractual', 'consent' & 'legitimate'
    base: {
        // Data required under the contractual legal base are required by the
        // client to perform the contract. Read more below.
        contractual: {
            // list of scopes
            scopes: [
                {
                    provider: "foo",
                    path: "bar",
                },
                {
                    provider: "foo",
                    path: "qux"
                }
            ],
            // list of usages of how this data will be used
            usages: [
                "Contractual description of how this data will be used",
            ]
        },

        // Data required under the consent legal base are optional and opt-out;
        consent: [
            // List of consent data, each with a different usage explaination
            {
                // list of scopes
                scopes: [
                    {
                        provider: "foo",
                        path: "bar",
                    },
                    {
                        provider: "foo",
                        path: "qux"
                    }
                ],
                // list of usages of how this data will be used
                usages: [
                    "Consent description of how this data will be used",
                ]
            },
            ...
        ],

        // Legitimate XXX
        legitimate: {
            // global reason why a legitimate request is performed
            reason: "Global description of why legitimate data are required",

            // List of legitimate requests
            groups: [
                {
                    // list of scopes
                    scopes: [
                        {
                            provider: "foo",
                            path: "bar",
                        },
                        {
                            provider: "foo",
                            path: "qux"
                        }
                    ],
                    // list of usages of how this data will be used
                    usages: [
                        "Legitimate description of how this data will be used",
                    ]
                }
            ],
        }
    },

    // List of legal metadata necessary to be fully compliant with GDPR/CCPA/...
    // legislation. See below for more detailled explaination of what each value
    // means.
    legal: {
        accept_users_right_access_modify_transfer_delete: false,
        automated_decision: false,
        automated_surveillance: false,
        destination: [
            "Destination #1",
            "Destination #2",
            ...
        ],
        email_dpo: "dpo@client.alias",
        evaluation_notation_rating_profiling: false,
        innovative_reasonable_expectations: false,
        mixing_reasonable_expectations: false,
        storage_duration: "1 year after revocation",
        subprocessors: [],
        tos_url: "https://client.alias/tos/",
        transfer_outside_eea: false,
    },
    network: {
        // optional. Protocol scheme. By default, 'https'.
        // in production, an authorization server MUST reject any contract which
        // scheme is not 'https'.
        scheme: 'https',

        // optional. Redirection endpoint after the authorization server
        // receives the agreement or denial of the user about a contract. By
        // default, '/alias/cb'.
        redirectEndpoint: '/alias/cb',

        // optional. Push endpoint where the granted data will be uploaded. By
        // default, '/alias/push/'
        pushEndpoint: '/alias/push',
    }
};

This contract (stored in variable contract) may be written into a file in its Anychain's token form.

fs.writeFileSync("contract.token", chain.toToken(contract));

Using a contract generator

We develop an Web UI to generate a contract. You just need to fill the form and you'll be able at the end of the process to generate the file contract.token.

Link to the contract generator

Add a Request button to your front-end

To identity a user, you'll need its alias. The client is responsible to get the user's alias, which will redirect the user-agent to the user's authorization server.

To request the user for access to its data, use the method Alias.request.

// in the HTML file, add in the tag <head>:
// <script type="application/javascript" src="https://cdn.gdpr.dev/anychain.js"></script>
// <script type="application/javascript" src="https://cdn.gdpr.dev/alias-client.js"></script>

const alias = "john.doe@gdpr.dev"
const contract = chain.await $.ajax("contract.json")

Alias.request(alias, contract)
    .catch((e) => {
        // on error
        alert("Impossible to log in with your alias: " + e);
    })

// on success, will redirect the user to its authorization server.

Handle the server response

After the user-agent's redirection, the authorization server will call back the client when the user made a decision, with a grant code or an error message. By default, the call back is a GET HTTP request to the redirection endpoint.

If the user denied or an error occured, the URL parameter error is set to one of the following values:

  • access_defined: The user denied the contract.

If the user agreed, the URL parameter code is set to the grant token. This should be persistently stored linked to your account of the user. It will be used to request your user's data.

Install the Alias data server

You'll need to run an Alias data server which will receive the granted data. The Alias data server is packaged as a Docker image.

First get the project and build it. Then, do

make build-docker

Now the image is ready, start an Alias data server.

docker run -v ./data:/data -p 8080:80 -p 3000:3000 alias/data-server
  • The port 8080 serves HTTP requests from the authorization server, which should be reverse-proxy from the app's web domain.
  • The port 3000 serves an HTTP REST API to let internal services fetch user's data.

3000 is a private port and should be accessed only by services which need to access the user's data. It MUST NOT be made public as it doesn't provide any credential mechanism, and anyone with a grant token would be able to access user's data.

The folder ./data will store the received granted data.

Fetch the user's data

Configure reverse-proxying

The autorization server will push the data to the client by performing HTTP requests on the client's Web domain (contract's field client.body.domain) and on endpoints whose base is /alias/push (or overwritten with contract's field network.pushEndpoint).

The traffic to the push endpoint should be reverse-proxy to the Alias client server.

# for nginx.
# SSL reverse-proxy
server {
    listen 443 ssl;
    listen [::]:443 ssl;

    ...

    # add the following to your nginx configuration
    location ^~ /alias {
        proxy_pass http://$upstream:8080;
    }
}

Get the data

A internal service can request any data from any valid grant token. To do so, perform a GET HTTP request on the /api/grant/%grant_hash%/path/to/file, where grant_hash is the root hash of the grant token.

const Anychain = require('@alias/anychain');
const chain = new Anychain();

// path of the file or folder to access
const path = "/path/to/file";

// build the file's URL
const grant = chain.fromToken(grantToken);
const grantHash = chain.fold(grant);
const url = `http://$upstream:3000/api/grant/${grantHash.base64()}${path}`;

fetch(url);

If the path is a file, the content of the file will be returned. If the path is a folder, a JSON array with every (recursive) children will be returned:

{status: 'ok', 'resources': ['/foo', '/bar/quz']}

If the data has not been received yet, the request will block and the Alias data server will request the authorization server to process the grant. If something's wrong, the Alias data server will return one of the following status:

  • HTTP status code 502: the authorization server is down. The transfer is aborted. Retry later.
  • HTTP status code 503: the transfer is happening but is taking too long to hold the connection opened. The client should retry after some time.
  • HTTP status code 504: the authorization server initiated a transfer but timed out. The transfer is aborted. Retry later.

XXX

  • more info about the legal contract
You can’t perform that action at this time.