REST and APIs

Carrying the Session Out of the Browser with ContinueAsRESTClient

Complete Program

The browser signs in, the session is handed to a client, the browser closes, and the client goes on calling the protected endpoint with nothing else running.

using GenerallyPositive;

using GenerallyPositive.Browser;

using static GenerallyPositive.Enums;


GPAL.WithPublishToConsole();


Selector username = GPAL.Selector.WithCSS("#username").WithSelectorName("Username").ToGPALObject();

Selector password = GPAL.Selector.WithCSS("#password").WithSelectorName("Password").ToGPALObject();

Selector submit = GPAL.Selector.WithCSS("#submit").WithSelectorName("Submit").ToGPALObject();


IBrowser browser = GPAL.Browser

.WithBrowserType(BrowserType.Chrome)

.WithAutomationEngine(AutomationEngine.PuppeteerPort)

.WithDriverLocation(@"C:drivers")

.ToGPALObject();


browser

.GoTo("https://www.example.com/login")

.WithSelector(username).FillInFrom("someone")

.WithSelector(password).FillInFrom("a password")

.WithSelector(submit).LeftClick();


// everything the browser earned, in a client that does not need it any more

browser

.ContinueAsRESTClient(out IRESTClient carried)

.Close(true);


string answer = carried

.WithEndpoint("/secure/orders")

.WithHttpMethod("GET")

.Execute(false);


GPAL.PublishSimpleEvent(GPALEventType.NOTICE, $"[{answer?.Length ?? 0}] characters, browser already closed");

Sign In However You Like

Nothing about the handoff cares how the session was earned. A filled-in login form, a profile that was already signed in, a credential given with WithCredentials, an anti-bot check cleared by a real navigation. Whatever the browser is holding when you ask is what comes across.

browser

.GoTo("https://www.example.com/login")

.WithSelector(username).FillInFrom("someone")

.WithSelector(password).FillInFrom("a password")

.WithSelector(submit).LeftClick();

Take the Client, Then Close the Browser

ContinueAsRESTClient is an out parameter in the middle of the chain, so the very next call can be Close. The client already knows the browser's origin, so it is ready to be given an endpoint rather than made to restate what the browser already knew.

browser

.ContinueAsRESTClient(out IRESTClient carried)

.Close(true);

WARNING

A browser that has not been anywhere has no origin to talk to and no cookies to carry. Asking for a client at that point publishes a warning and hands back one that will fail later at the request. Go somewhere first.

What Comes Along

The origin, the cookies scoped to it, the user agent and accept language the browser is really sending, and the credential it was given with WithCredentials. That is enough for any site whose wall is the cookie jar, which is most of them.

string answer = carried

.WithEndpoint("/secure/orders")

.WithHttpMethod("GET")

.Execute(false);


int status = ((RESTClient)carried).StatusCode;

What Does Not

The TLS handshake stays with the browser. A client outside it makes its own connection with its own fingerprint, so a site that inspects the transport rather than the cookie will refuse it even though every header is right. When that happens the answer is .Fetch, which issues the request from inside the page and carries the whole thing.

// refused by an edge that fingerprints the transport

carried.WithEndpoint("/secure/orders").Execute(false);


// the same call, issued by the page, carrying everything

browser.Fetch(ordersRequest).SaveTo(ref json);

NOTE

If the carried client gets in, the site is checking a cookie. If it gets a deny page and the in-page fetch gets data, the site is fingerprinting the transport. Two calls tell you which wall you are standing at.

Prove It Actually Carried Something

A pass means nothing without a control. Stand a plain client next to the carried one, pointed at the same endpoint with nothing behind it. If the plain one gets in too, the site was never asking for credentials and the carried one proved nothing.

IRESTClient plain = (IRESTClient)GPAL.RESTClient.WithAPIBase("https://www.example.com").ToGPALObject();


string carriedAnswer = carried.WithEndpoint("/secure/orders").Execute(false);

string plainAnswer = plain.WithEndpoint("/secure/orders").Execute(false);

Replay a Call the Page Made

The handoff pairs with CaptureCallTemplate. Capture the request the page made, hand its headers to the carried client, fill in whatever varied, and issue it with no browser in the path. It is the fast route wherever the session is all a site asks for.

browser

.WithCallFilter("/api/rates")

.CaptureCallTemplate(out GPALRequest rates);


browser.ContinueAsRESTClient(out IRESTClient client);


foreach (KeyValuePair<string, string> header in rates.Headers)

client.WithHeader(header.Key, header.Value);


string answer = client

.WithEndpoint(rates.Path)

.WithParameters(rates.Body)

.WithHttpMethod("POST")

.Execute(false);

💬 Ask GPAL