Kroger...

Authorization Tutorial

In This Tutorial

This tutorial covers how to implement the authorization code flow using the /authorize and /token endpoints.

Introduction to Authorization Code Flow

The authorization code flow is a multi-process grant type, meaning that the client must perform two requests:

  1. The client must call the /authorize endpoint to request an authorization code. This step requires authentication of the end user.
  2. The client must call the /token endpoint using the authorization code as a query parameter.

If the client is authenticated and successfully completes both of these requests, the server will return both an access and refresh token. The access token is required when clients makes requests to all other supported Kroger API endpoints. The refresh token allows the client to obtain a new access token once the original access token has expired.

Setting Configuration Variables

The client ID, client secret, base URL, and pre-configured redirect URL are specified in the environment file below.

.env file (server side)

CLIENT_ID=[client ID]
CLIENT_SECRET=[client secret]
OAUTH2_BASE_URL=https://api.kroger.com/v1/connect/oauth2
API_BASE_URL=https://api.kroger.com
REDIRECT_URL=http://localhost:3000/callback
Always keep your client secret private.

The app uses configuration variables to easily point to different environments. You should never store your client secret in a client-side JavaScript file.

config.js file (client side)

// Parameters imported from .env file (environment variables)
const apiBaseUrl = process.env.API_BASE_URL;
const oauth2BaseUrl = process.env.OAUTH2_BASE_URL;
const clientId = process.env.CLIENT_ID;
const redirectUrl = process.env.REDIRECT_URL;

export default { apiBaseUrl, oauth2BaseUrl, clientId, redirectUrl };

Authenticating the End User

Required query string parameters for the authorization request are:

  • Client Id
  • Redirect URI
  • Response type
  • Scope

An example authorize request is show below.

Example Authorization Request

curl -X GET \
https://api.kroger.com/v1/connect/oauth2/authorize?scope={{SCOPE}}&response_type=code&client_id={{CLIENT_ID}}&redirect_uri={{REDIRECT_URI}} \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' 

When the end user clicks the "Sign In" button in the demo application, a redirect is issued to the OAuth2 authorization page. The authorization page is used to authenticate the user and request permission to their resources as illustrated in the Obtain Authorization from User section of the tutorial. The demo app construction and execution of the authorization request is shown in the code below.

authentication.js file (client side)

// Authorization code redirect initiated by 'login' event from Sign In button
function redirectToLogin() {
    // Must define all scopes needed for application
    const scope = encodeURIComponent('product.personalized cart.basic:rw profile.full');
    // Build authorization URL
    const url =
        // Base URL (https://api.kroger.com/v1/connect/oauth2)
        `${config.oauth2BaseUrl}/authorize?` +
        // ClientId (specified in .env file)
        `client_id=${encodeURIComponent(config.clientId)}` +
        // Pre-configured redirect URL (http://localhost:3000/callback)
        `&redirect_uri=${encodeURIComponent(config.redirectUrl)}` +
        // Grant type
        `&response_type=code` +
        // Scope specified above
        `&scope=${scope}`;
    // Browser redirects to the OAuth2 /authorize page
    window.location = url;
}
...

Obtaining Authorization from the User

As outlined in the code above, the browser will redirect the user to the following OAuth2 authorization Sign In page.

SignIn

After the user credentials are authenticated, the user is asked to authorize access to their account. The demo app sends the following scope.

scope = encodeURIComponent('product.personalized cart.basic:rw profile.full');

The scope allows the application to access all of the information specified in the authorization page below.

Authorize

Exchanging the Auth Code for Tokens

When the user authorizes the request, the user is then redirected back to the demo application with an authorization code.

Example returned URL

http://JsDemoApp.com/cb?code=AUTHORIZATION_CODE

The callbackHandler function parses the returned URL to obtain the auth code, which is passed to the tokenService.getByAuth(params.code) function.

callbackHandler.js file (server side)

export async function callbackHandler(req, res, next) {
    // Parse out auth code returned from authorization server
    let params = url.parse(req.url, true).query;
    if (!params.code) {
        res.sendStatus(400);
        return;
    }
    let token = await tokenService.getByAuth(params.code);
    ...
}

...

An example token request is shown below.

Example Token request

curl -X POST \
  https://api.kroger.com/v1/connect/oauth2/token \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id={{CLIENT_ID}}&client_secret={{CLIENT_SECRET}}&grant_type=authorization_code&code={CODE}&redirect_uri={{REDIRECT_URI}}'

The example shown below in the token-service.js file builds and executes the token request. The authorization code is passed as a query parameter in the async function getByAuth(code) function.

The query parameters should be URL encoded.

token-service.js (server side)

// Parameters imported from .env environment variables
const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;
const redirectUrl = process.env.REDIRECT_URL;
// Get token by authorization code (getByAuth)
async function getByAuth(code) {
  const body = `grant_type=authorization_code&code=${encodeURIComponent(
    code
  )}&redirect_uri=${encodeURIComponent(redirectUrl)}`;
  return await get(body);
}
// Get token using refresh token
async function getByRefresh(refreshToken) {
  const body =
    `grant_type=refresh_token&` +
    `refresh_token=${encodeURIComponent(refreshToken)}`;
  return await get(body);
}

async function get(body) {
  // ClientId and ClientSecret (stored in .env file)
  const encoded = buffer.Buffer.from(`${clientId}:${clientSecret}`, `ascii`);
  // ClientId and clientSecret must be encoded
  const authorization = "Basic " + encoded.toString("base64");
  // Base URL (https://api.kroger.com/v1/connect/oauth2)
  // Version/Endpoint (/v1/token)
  const tokenUrl = `${process.env.OAUTH2_BASE_URL}/token`;

  // token request
  let tokenResponse = await fetch(tokenUrl, {
    method: "POST",
    headers: {
      "User-Agent": "",
      Authorization: authorization,
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: body
  });
  // Handle response
  if (tokenResponse.status >= 400) {
    console.log(`tokenResponse error: ${tokenResponse.status}`);
    throw new Error(`tokenResponse failed with status ${tokenResponse.status}`);
  }
  // Return json object
  return await tokenResponse.json();
}

The response returns both the access and refresh token.

A sample JSON response is shown below.

Example json object structure

{
    "refresh_token": "FN2Y0baF2EWC3bPMWdemBwwng0ZmX8",
    "expires_in": 172800,
    "access_token": "B2eiLh4F2EwCJ4iMWb7mMbbng0Zm03",
    "token_type": "bearer"
}

The access and refresh token are stored as cookies by the callbackHandler.js file discussed earlier.

callbackHandler.js file (server side)

...
        // The access and refresh token are stored as cookies
        res.cookie('accToken', token.access_token);
        res.cookie('refToken', token.refresh_token);
        // Redirect user back to browser page (index.html)
        res.redirect('/');
    } catch (error) {
        console.log(`error: ${error}`);
        res.sendStatus(500);
    }
}

Using the Access Token

Once the server has generated the access and refresh token, the client side extracts the tokens from the cookies. The access token can now be used by the application to make API requests to supported Kroger endpoints.

authentication.js file (client side)

...

// Handle the call back from authorization server
function handleCallback() {
    const accessToken = cookies.get('accToken');
    const refreshToken = cookies.get('refToken');

    if (!accessToken) {
        return false;
    }
    // Store tokens client side for API requests
    storeTokens(accessToken, refreshToken);

    cookies.remove('accToken');
    cookies.remove('refToken');

    return true;
}

Next Steps

Now that you have seen one possible implementation of the OAuth2 framework, we recommend viewing the Refresh Token Tutorial. The refresh tutorial covers how to store and use a refresh token to get a new access token once the original has expired.