Web API Authorization Guide

In this guide we explore how to obtain a valid OAuth access token in order to send HTTP requests to the Crowdynews Web APIs.

This guide contains the following topics:

Roles

This guide uses the following vocabulary to describe roles:

ROLE ROLE DESCRIPTION
Consumer The entity on behalf of whom the HTTP requests are made. For example, a user or machine.
Client The entity making the HTTP requests. For example, a JavaScript Web Application or Web API.

Supported Authorization Flows

The Crowdynews Web APIs can either be consumed by users or machines:

  • Users

    For example, your client is a JavaScript Web Application where a user can moderate a content queue via the Content API.

  • Machines

    For example, your client is a Web API that creates collections via the Control API.

Depending on if HTTP requests are made on behalf of users or machines, the access token sent with a request will be obtained by either using the Implicit Grant Flow (user) or the Client Credentials Grant Flow (machine).

Implicit Grant Flow

This flow is for clients that are implemented entirely using JavaScript and are running in the user’s browser. You don't need any server-side code to use it. It must be used by clients that want to make requests to the Crowdynews Web APIs on behalf of a user.

The most notable difference compared to other authorization flows is that there is no refresh token provided. The reason being that the client can't guarantee to keep its client credentials a secret. Therefore only the client identifier is required to obtain an access token. Also note that the user and client must both be registered with the Crowdynews Authorization API. After doing this you'll receive your unique client identifier and users will be able to gain access to your client. You can register by contacting [email protected].

This flow is implemented according to RFC-6749.

Implicit Grant Flow

1. Your client requests authorization

Make a request to:

GET https://auth.crowdynews.com/v1/authorize

The following request query parameters are required:

QUERY PARAMETER VALUE DESCRIPTION
response_type Must be set to token.
client_id The client identifier provided to you by Crowdynews after registering your client.
redirect_uri The URI to redirect to after the user grants or denies permission. This URI needs to be provided to Crowdynews when you register your client. This URI usually points to where the authorization request originated from.

For example:

GET https://auth.crowdynews.com/v1/authorize?response_type=token&client_id=9173409a5f69fa5f8d169b1000cf269f&redirect_uri=https://my-app.com/callback

2. The user is asked to authorize access

If the user is not logged in, they are prompted to do so using their Crowdynews username and password. Then the Authorization API will present details of what user information will be accessed by the client by presenting a confirmation dialog.

3. The user is redirected back to your specified redirect URI

After the user grants or denies the client access, the Authorization API will redirect the user to the URI that was specified as the redirect_uri query paramater in step 1. For our example, this would be the address:

https://my-app.com/callback

If the user has granted access, the final URL, i.e. the redirect URI, will contain a hash fragment with the following data encoded as a query string:

FRAGMENT VALUE DESCRIPTION
access_token An access token that can be provided in subsequent calls to the Crowdynews Web APIs.
token_type The type of access token that was issued. Will always be set to Bearer.

For example:

https://my-app.com/callback#access_token=d0944f4ea3ee303740b4dfb400c8eb3ff833ebfc98d7829534c17ea42cdaecbd333e0062204c007c993b1c3768c4be2b70120e70c353ed83c9ef7f06000c4bef&token_type=Bearer

4. Store and use the obtained access token

The client can now extract the access token from the hash fragment described in step 3 and store it using, for example, the web browser Local Storage API. See the reference implementation for an approach. The client can now make authenticated requests.

Note that access tokens are deliberately set to expire after a short time. When it does, the consumer needs to go through the entire implicit grant flow to obtain a new and valid access token.

Client Credentials Grant Flow

This flow is for clients that are implemented on the server-side. It must be used by clients that want to make requests to the Crowdynews Web APIs on behalf of themselves. This means the client is also the consumer.

The most notable difference compared to other authorization flows is that there's no authorization included, because there is no user involved. Also note that the client must be registered with the Crowdynews Authorization API. After doing this you'll receive your unique client identifier and client secret which can be used by your client. You can register by contacting [email protected].

This flow is implemented according to RFC-6749.

Client Credentials Grant Flow

1. Your client requests tokens

Make a request to:

POST https://auth.crowdynews.com/v1/oauth/token

The following request headers are required:

HEADER FIELD VALUE DESCRIPTION
Content-Type The content type of the request body. This field must be set to application/json.
Authorization* Base 64 encoded string that contains the client identifier and client secret provided to you by Crowdynews after registering your client. This field must have the format: Authorization: Basic <base64 encoded clientId:clientSecret>.

* See the reference implementation for an approach on how to construct this header.

The following request body parameters are required:

REQUEST BODY DATA VALUE TYPE VALUE DESCRIPTION
grant_type String Must be set to client_credentials.

Example request:

Content-Type: application/json
Authorization: Basic ODcwNDQxOWQ1ZTYxY2Q1NmU1NzRiNjJmMjk4MmRhMzY6OWI5MTZiYzJiNGJhOTY5ZjM4MWEyYzkyMzVmN2IyNWI1ODJmOTFhMGY0ODRiNTJhZDZlMzE5OWY5NWE4MDk5OQ==

POST https://auth.crowdynews.com/v1/oauth/token

{
    "grant_type": "client_credentials"
}

On success, the HTTP status code in the response header will be 200 OK and the response body will contain the tokens in JSON format. On error, the header status code will be an error code and the response body will contain an error object.

RESPONSE BODY DATA VALUE TYPE VALUE DESCRIPTION
access_token String The token to use to make authenticated requests.
refresh_token String The token to use to obtain a new access token when one expired.
token_type String The type of the issued tokens. Will always be set to Bearer.

Example response:

HTTP/1.1 200

{
    "access_token": "e0933f4ea3ee303740b4dfb400c8eb3ff833ebfc98d7829534c17ea42cdaecbdfffe0062204c007c663b1c3768c4be2b71130ef0c353ed83c9ef7f06040c4ddf",
    "refresh_token": "78f3e68d303ffa626a766692b508be5c3c3b8a6156110fdca7c60b65ece2c144132f2f8f3c3743eef6f94fed26f3bfb303461a854b73adb0785e2f664f53e888",
    "token_type": "Bearer"
}

2. Store and use the obtained access token

The client can now extract the access token from the response body described in step 1 and store it in, for example, a database. See the reference implementation for an approach. The client can now make authenticated requests.

3. Refresh the access token after expiration

Access tokens are deliberately set to expire after a short time, after which new tokens may be granted by supplying the refresh token that was obtained during the client credentials exchange as described in step 1.

Make a request to:

POST https://auth.crowdynews.com/v1/oauth/token

The following request headers are required:

HEADER FIELD VALUE DESCRIPTION
Content-Type The content type of the request body. This field must be set to application/json.
Authorization* Base 64 encoded string that contains the client identifier and client secret provided to you by Crowdynews after registering your client. This field must have the format: Authorization: Basic <base64 encoded clientId:clientSecret>.

* See the reference implementation for an approach on how to construct this header.

The following request body parameters are required:

REQUEST BODY DATA VALUE TYPE VALUE DESCRIPTION
grant_type String Must be set to refresh_token.
refresh_token* String The token used to refresh the access token.

* The refresh token has been obtained in step 1.

Example request:

Content-Type: application/json
Authorization: Basic ODcwNDQxOWQ1ZTYxY2Q1NmU1NzRiNjJmMjk4MmRhMzY6OWI5MTZiYzJiNGJhOTY5ZjM4MWEyYzkyMzVmN2IyNWI1ODJmOTFhMGY0ODRiNTJhZDZlMzE5OWY5NWE4MDk5OQ==

POST https://auth.crowdynews.com/v1/oauth/token

{
    "grant_type": "refresh_token",
    "refresh_token": "78f3e68d303ffa626a766692b508be5c3c3b8a6156110fdca7c60b65ece2c144132f2f8f3c3743eef6f94fed26f3bfb303461a854b73adb0785e2f664f53e888"
}

On success, the HTTP status code in the response header will be 200 OK and the response body will contain the refreshed access- and refresh tokens in JSON format. On error, the header status code will be an error code and the response body will contain an error object.

RESPONSE BODY DATA VALUE TYPE VALUE DESCRIPTION
access_token Boolean The token to use to make authenticated requests.
refresh_token Boolean The token to use to obtain a new access token when one expired.
token_type String The type of the issued tokens. Will always be set to Bearer.

Example response:

HTTP/1.1 200

{
    "access_token": "b3795396b638f3ef1658c409f474c280131663b8ca40b81f20f29659bc703fe14fcefd739a2f1a1e858bae32e819fad8e47ee1f1a1634493a0c544009c26d6ec",
    "refresh_token": "99a99f9f0f3b529ffa0f8efc5e04d1dc0492133f19d474fc26ff688b2bc6127344ee639a0fccf1ac748947a29a7bdc4b1a5648e4da27e6639656612895c50aee",
    "token_type": "Bearer"
}

Requesting Access Token Information

After an access token has been obtained, by means of any authorization flow whatsoever, said access token can be used to request information from the Authorization API.

Expiration Details

In order to get the expiration details of an access token, make a request to:

GET https://auth.crowdynews.com/v1/oauth/token/info

The following request query parameters are required:

QUERY PARAMETER VALUE DESCRIPTION
access_token The access token of which expiration information is requested.

Example request:

GET https://auth.crowdynews.com/v1/oauth/token/info?access_token=cf765396b638f3ef1668c409f474c280131663b8ca40b81f20f29659bc703fe14fcefd739a2f1a1e858bae32e819fad8e47ee1f1a1674493a0c544009c26d6ef

On success, the HTTP status code in the response header is 200 OK and the response body will contain the access token expiration details in JSON format. On error, the header status code is an error code and the response body will contain an error object.

RESPONSE BODY DATA VALUE TYPE VALUE DESCRIPTION
active Boolean If the access token is active or not.
expired Boolean If the access token has expired or not.
expires String The timestamp denoting when the access token will expire.
ttl Number The access token time to live in milliseconds. This property is 0 when an access token has expired.

Example response:

HTTP/1.1 200

{
    "active": true,
    "expired": false,
    "expires": "2016-06-14T11:27:45.568Z",
    "ttl": 3577240
}

Consumer Details

In order to get details about the consumer associated with an access token, make a request to:

GET https://auth.crowdynews.com/v1/oauth/me

The following request query parameters are required:

QUERY PARAMETER VALUE DESCRIPTION
access_token The access token of which consumer information is requested.

Example request:

GET https://auth.crowdynews.com/v1/oauth/me?access_token=b3795396b638f3ef1658c409f474c280131663b8ca40b81f20f29659bc703fe14fcefd739a2f1a1e858bae32e819fad8e47ee1f1a1634493a0c544009c26d6ec

On success, the HTTP status code in the response header is 200 OK and the response body will contain the consumer details in JSON format. On error, the header status code is an error code and the response body will contain an error object.

Note that the returned details will differ depending on which authorization flow was used to obtain the access token, i.e. if it was obtained on behalf of a user or machine.

RESPONSE BODY DATA VALUE TYPE VALUE DESCRIPTION
privileges Array Contains the consumer's access privileges. Each element is a string denoting a privilege.
consumer_id String A hexadecimal string to uniquely identify the consumer.
consumer_name String A human readable consumer identifier.
consumer_type String The type of consumer. Will either be user or client (machine).
consumer_email String A unique email address associated with the consumer.
language String Optional. A valid ISO 639-1 language code. A language will only be returned when the consumer is a user.

Example response:

HTTP/1.1 200

{
    "privileges": [ ],
    "consumer_id": "9fd4d467d438e429f48e416e2f361d31",
    "consumer_name": "Mr. Robot",
    "consumer_type": "client",
    "consumer_email": "[email protected]"
}

In case the access token is associated with a user, an example response might be:

HTTP/1.1 200

{
    "privileges": [
        "MY_ACCOUNT"
    ],
    "consumer_id": "569f34286f24a9f969499adf",
    "consumer_name": "John Doe",
    "consumer_type": "user",
    "consumer_email": "[email protected]",
    "language": "en"
}

Reference implementations

The following example illustrates an approach to implement the above described authorization flows in JavaScript.

Implicit Grant reference

<!DOCTYPE html>

<html>
    <head>
        <meta charset="utf-8">

        <title>Implicit Grant</title>
    </head>

    <body>
        <section>
            <p>Open your console.</p>
        </section>

        <script type="text/javascript">
            'use strict';

            const AUTHORIZATION_API = 'https://auth.crowdynews.com/v1/oauth';
            const CLIENT_ID = '9173409a5f69fa5f8d169b1000cf269f';
            const REDIRECT_URI = 'https://my-app.com';

            function getAccessToken() {
                return localStorage.getItem('accessToken');
            }

            function getUserDetails() {
                const user = localStorage.getItem('user');

                try {
                    return JSON.parse(user);
                } catch (err) {
                    console.log('Parse Error: user details is invalid JSON');
                }
            }

            function extractTokenFromUrl() {
                const match = /access_token=([a-z0-9]+)/.exec(window.location.hash);
                const accessToken = match ? match[1] : undefined;

                if (accessToken) {
                    window.location.hash = '';
                }

                return accessToken;
            }

            function authorizeRedirect() {
                const url = `${AUTHORIZATION_API}/authorize?response_type=token&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}`;

                window.location.replace(url);
            }

            function validateToken(token, next) {
                const url = `${AUTHORIZATION_API}/me?access_token=${token}`;
                const xhr = new XMLHttpRequest();

                xhr.onreadystatechange = function () {
                    const xhrIsDone = xhr.readyState === 4;
                    const statusIsOk = xhr.status === 200;
                    const tokenIsInvalid = xhr.status === 401;

                    if (xhrIsDone && !statusIsOk) {
                        return next(
                            new Error(xhr.responseText)
                        );
                    }

                    if (xhrIsDone && tokenIsInvalid) {
                        return authorizeRedirect();
                    }

                    if (xhrIsDone && statusIsOk) {
                        return next(null, xhr.responseText);
                    }
                };

                xhr.open('GET', url, true);
                xhr.send();
            }

            function start(done) {
                const token = extractTokenFromUrl() || getAccessToken();

                if (!token) {
                    return authorizeRedirect();
                }

                validateToken(token, (err, userDetails) => {
                    if (err) {
                        return done(err);
                    }

                    localStorage.setItem('user', userDetails);
                    localStorage.setItem('accessToken', token);

                    return done();
                });
            }

            start(err => {
                if (err) {
                    console.log(`Error: ${err}`);
                } else {
                    const userDetails = getUserDetails();
                    const accessToken = getAccessToken();

                    console.log(`user ${userDetails.consumer_email} has successfully logged in`);
                    console.log(`with access token ${accessToken}`);
                }
            });
        </script>
    </body>
</html>

Client Credentials Grant reference

'use strict';

const request = require('request');

const AUTHORIZATION_API = 'https://auth.crowdynews.com/v1/oauth/token';
const CLIENT_ID = '8173f09a5f69ff5f8d169b1333cf264e';
const CLIENT_SECRET = 'ff813bc2b4ba9691381a2c923597b25b582e91a05494b52ad6e3199f95a80255';

function createAuthorizationHeaderValue(clientId, clientSecret) {
    const encoded = `${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`;
    const buffer = new Buffer(encoded).toString('base64');

    return `Basic  ${buffer}`;
}

request.post({
    url: AUTHORIZATION_API,
    json: true,
    headers: {
        Authorization: createAuthorizationHeaderValue(CLIENT_ID, CLIENT_SECRET)
    },
    body: {
        grant_type: 'client_credentials'
    }
}, (err, res, body) => {
    if (err) {
        console.log(`error: ${err}`);
    }

    console.log(`access token: ${body.access_token}`);
    console.log(`refresh token: ${body.refresh_token}`);
});