Ping SDKs

Intercept and modify REST calls

Applies to:

  • Ping SDK for Android

  • Ping SDK for iOS

  • Ping SDK for JavaScript

The Ping SDKs support modification of REST calls before they are sent.

For example, you can customize:

  • Request parameters

  • Headers

  • Cookies

  • Request URLs

  • Request methods

  • The request body and post data

Request interceptors

Each SDK provides an interface that you can use to customize requests:

  • Android

  • iOS

  • JavaScript

public interface FRRequestInterceptor<Action> {
    @NonNull Request intercept(Request request, Action action);
}

public class Action {
    private String type;
    private JSONObject payload;
}
public protocol RequestInterceptor {
    func intercept(request: Request, action: Action) -> Request
}

public struct Action {
    public let type: String
    public let payload: [String: Any]?
}
type RequestMiddleware = (req: RequestObj, action: Action, next: () => RequestObj) => void;

interface RequestObj {
  url: URL;
  init: RequestInit;
}

interface Action {
  type: string;
  payload?: any; // optional data
}

Request interceptors have two inputs:

The Request object

Represents the original request, and has information about the body, method type, parameters, and more.

  • Android

  • iOS

  • JavaScript

public class Request {
    public URL url();
    public Iterator<Pair<String, String>> headers();
    public String header(String name);
    public List<String> headers(String name);
    public String method();
    public Object tag();
    public Body body();
    public Builder newBuilder();
}

// Use Build to build upon on existing Request
public class Builder {
   public Builder url(URL url);
   public Builder url(String url);
   public Builder header(String name, String value);
   public Builder addHeader(String name, String value);
   public Builder removeHeader(String name);
   public Builder get();
   public Builder put(Body body);
   public Builder post(Body body);
   public Builder delete(Body body);
   public Builder delete();
   public Builder patch(Body body);
   public Request build();

}
public struct Request {
    // Properties
    public let url: String
    public let method: HTTPMethod
    private(set) public var headers: [String: String]
    public let bodyParams: [String: Any]
    public let urlParams: [String: String]
    public let responseType: ContentType
    public let requestType: ContentType
    public let timeoutInterval: Double

    public enum ContentType: String {
        case plainText = "text/plain"
        case json = "application/json"
        case urlEncoded = "application/x-www-form-urlencoded"
    }

    public enum HTTPMethod: String {
        case GET = "GET"
        case PUT = "PUT"
        case POST = "POST"
        case DELETE = "DELETE"
    }

    public func build() -> URLRequest?
}

Refer to the native JavaScript Request object in the MDN Web Docs.

The Action object

Represents the type of operation the request performs:

Action Description

START_AUTHENTICATE

Initial call to an authentication tree

AUTHENTICATE

Proceed through an authentication tree flow

AUTHORIZE

Obtain authorization token from PingAM

EXCHANGE_TOKEN

Exchange authorization code for an access token

REFRESH_TOKEN

Refresh an access token

REVOKE_TOKEN

Revoke a refresh or access token

LOGOUT

Log out a session

USER_INFO

Obtain information from the userinfo endpoint

PUSH_REGISTER

Register a push device with PingAM; for example, a call to /json/push/sns/message?_action=register

PUSH_AUTHENTICATE

Authenticate using push; for example, a call to /json/push/sns/message?_action=authenticate

The AUTHENTICATE and START_AUTHENTICATE actions have a payload that contains:

tree

The name of the authentication tree being called.

type

Whether the call is to a service, or is in response to composite_advice.

The outcome of applying a request interceptor is the entire modified request object, ready to either be sent to PingAM, or to have additional request interceptors applied.

Examples

This section covers how to develop request interceptors, referred to as "middleware" in the Ping SDK for JavaScript, and apply them to outbound requests from your applications.

Ping SDK for Android

Query parameters and headers

The example sets the ForceAuth query parameter to true, and adds an Accept-Language header with a value of en-GB on all outgoing requests of the START_AUTHENTICATE type:

public class QueryParamsAndHeaderRequestInterceptor implements FRRequestInterceptor<Action> {
    @NonNull
    @Override
    public Request intercept(@NonNull Request request, Action tag) {
        if (tag.getType().equals(START_AUTHENTICATE)) {
            return request.newBuilder()
                    // Add query parameter:
                    .url(Uri.parse(request.url().toString())
                            .buildUpon()
                            .appendQueryParameter("ForceAuth", "true").toString())

                    // Add additional header:
                    .addHeader("Accept-Language", "en-GB")

                    // Construct the updated request:
                    .build();
        }
        return request;
    }
}

To register the request interceptor, use the RequestInterceptorRegistry.getInstance().register() method:

RequestInterceptorRegistry.getInstance().register(new QueryParamsAndHeaderRequestInterceptor())

Any calls the app makes to initiate authentication now have the query parameter ForceAuth=true appended, and include an accept-language: en-GB header added.

Cookies

The example adds a custom cookie to outgoing requests:

public class CustomCookieInterceptor implements FRRequestInterceptor<Action>, CookieInterceptor {
    @NonNull
    @Override
    public Request intercept(@NonNull Request request) {
       return request;
    }

    @NonNull
    @Override
    public Request intercept(@NonNull Request request, Action tag) {
        return request;
    }

    @NonNull
    @Override
    public List<Cookie> intercept(@NonNull List<Cookie> cookies) {
        List<Cookie> newCookies = new ArrayList<>();
        newCookies.addAll(cookies);
        newCookies.add(
          new Cookie.Builder()
            .domain("example.com")
            .name("member").value("gold")
            .httpOnly().secure().build()
        );

        return newCookies;
    }
}

You can register multiple request interceptors as follows:

RequestInterceptorRegistry.getInstance().register(
    new QueryParamsAndHeaderRequestInterceptor(),
    new CustomCookieInterceptor()
);

Ping SDK for iOS

Query parameters and headers

The example sets the ForceAuth query parameter to true, and adds an Accept-Language header with a value of en-GB on all outgoing requests of the AUTHENTICATE or START_AUTHENTICATE type:

class QueryParamsAndHeaderRequestInterceptor: RequestInterceptor {
    func intercept(request: Request, action: Action) -> Request {
        if action.type == "START_AUTHENTICATE" || action.type == "AUTHENTICATE" {
            // Add query parameter:
            var urlParams = request.urlParams
            urlParams["ForceAuth"] = "true"

            // Add additional header:
            var headers = request.headers
            headers["Accept-Language"] = "en-GB"

            // Construct the updated request:
            let newRequest = Request(
                url: request.url,
                method: request.method,
                headers: headers,
                bodyParams: request.bodyParams,
                urlParams: urlParams,
                requestType: request.requestType,
                responseType: request.responseType,
                timeoutInterval: request.timeoutInterval
            )
            return newRequest
        }
        else {
            return request
        }
    }
}

To register the request interceptor, use the registerInterceptors() method:

FRRequestInterceptorRegistry.shared.registerInterceptors(
    interceptors: [
        QueryParamsAndHeaderRequestInterceptor()
    ]
)

Any calls the app makes to initiate authentication now have the query parameter ForceAuth=true appended, and include an accept-language: en-GB header added.

Cookies

The example adds a custom cookie to outgoing requests:

class CookieInterceptor: RequestInterceptor {
    func intercept(request: Request, action: Action) -> Request {
        if action.type == "START_AUTHENTICATE" || action.type == "AUTHENTICATE" {

            var headers = request.headers
            headers["Cookie"] = "member=gold; level=2"

            let newRequest = Request(
                url: request.url,
                method: request.method,
                headers: headers,
                bodyParams: request.bodyParams,
                urlParams: request.urlParams,
                requestType: request.requestType,
                responseType: request.responseType,
                timeoutInterval: request.timeoutInterval)
            return newRequest
        }
        else {
            return request
        }
    }
}

You can register multiple request interceptors as follows:

FRRequestInterceptorRegistry.shared.registerInterceptor(
    interceptors: [
        QueryParamsAndHeaderRequestInterceptor(),
        CookieInterceptor()
    ]
)

Ping SDK for JavaScript

The example has two middleware configurations. One sets the ForceAuth query parameter to true, the other adds an Accept-Language header with a value of en-GB on all outgoing requests of the START_AUTHENTICATE type:

const forceAuthMiddleware = (
    req: RequestObj,
    action: Action,
    next: () => RequestObj
): void => {
    switch (action.type) {
        case 'START_AUTHENTICATE':
            req.url.searchParams.set('ForceAuth', 'true');
            break;
    }
    next();
};

const addHeadersMiddleware = (
    req: RequestObj,
    action: Action,
    next: () => RequestObj
): void => {
    switch (action.type) {
        case 'START_AUTHENTICATE':
            const headers = req.init.headers as Headers;
            headers.append('Accept-Language', 'en-GB');
            break;
    }
    next();
};

Apply the middleware in the config:

Config.set({
    clientId: 'sdkPublicClient',
    middleware: [
        forceAuthMiddleware,
        addHeadersMiddleware
    ],
    redirectUri: 'https://localhost:8443/callback.html',
    realmPath: 'alpha',
    scope: 'openid profile email address',
    serverConfig: {
        baseUrl: 'https://openam-forgerock-sdks.forgeblocks.com/am',
        timeout: 30000
    },
    tree: 'UsernamePassword'
});

Any calls the app makes to start authentication now have the query parameter and header added.

You can only modify headers in certain types of request.

For example START_AUTHENTICATE and AUTHENTICATE types, but not AUTHORIZE types as they occur in an iframe.