public static async Task> Login(NadexAPICredentials credentials, ILogger? log = null, CancellationToken ct = default) { var cookies = new CookieContainer(); var handler = new HttpClientHandler { CookieContainer = cookies, UseCookies = true, UseDefaultCredentials = false, }; var client = new HttpClient(handler, disposeHandler: true) { Timeout = TimeSpan.FromSeconds(Debugger.IsAttached ? 10000 : 3), BaseAddress = credentials.Username.AccountType switch { NadexAccountType.Demo => new("https://demo-trade.nadex.com"), NadexAccountType.Live => new("https://trade.nadex.com"), _ => throw credentials.Username.AccountType.UnknownValueException(), } }; using var request = new HttpRequestMessage(HttpMethod.Post, "iDeal/v2/security/authenticate"); // Copied from developer tools, network tab, in Edge browser. // Content-type and Content-Length headers were removed as they are attached separately to request.Content var headerValues = """ accept-language: en-US,en;q=0.9 cache-control: no-cache origin: https://platform.nadex.com pragma: no-cache sec-ch-ua: "Microsoft Edge";v="107", "Chromium";v="107", "Not=A?Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" sec-fetch-dest: empty sec-fetch-mode: cors sec-fetch-site: same-site user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35 x-device-user-agent: vendor=IG | applicationType=Nadex | platform=web | deviceType=phone | version=0.856.0+66843dd8 """; foreach (var line in headerValues.Split('\n').Select(l => l.Trim())) { var parts = line.Split(": ", 2); request.Headers.Add(parts[0], parts[1]); } var content = new { username = credentials.Username.Value.ToLowerInvariant(), password = credentials.Password }; request.Content = JsonContent.Create(content, content.GetType(), null, new JsonSerializerOptions(JsonSerializerDefaults.Web)); HttpResponseMessage? response = null; try { response = await client.SendAsync(request, ct); var responseContent = await response.Content.ReadAsStringAsync(ct); var shortResponseContent = responseContent.LimitLength(1000); if (response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) { // BAM!! here's where it goes wrong when container is running on an Ubuntu server in the cloud. log?.LogError($"{response.StatusCode} {shortResponseContent}"); return new(NotAuthorized.Instance); } response.EnsureSuccessStatusCode(); var xSecurityToken = response.Headers .Where(h => h.Key == "X-SECURITY-TOKEN") .Select(h => h.Value.FirstOrDefault()) .Where(value => !string.IsNullOrWhiteSpace(value)) .FirstOrDefault(); var jSessionId = cookies.GetAllCookies() .Where(c => c.Name == "JSESSIONID") .Select(c => c.Value) .Where(value => !string.IsNullOrWhiteSpace(value)) .FirstOrDefault(); if (string.IsNullOrWhiteSpace(xSecurityToken) || string.IsNullOrWhiteSpace(jSessionId)) { log?.LogError($"Missing tokens: {shortResponseContent}"); return new(NotAuthorized.Instance); } string accountId, lightstreamerEndpoint; try { var json = JsonSerializer.Deserialize(responseContent); accountId = json.GetProperty("currentAccountId").GetString()!; lightstreamerEndpoint = json.GetProperty("lightstreamerEndpoint").GetString()!; if (string.IsNullOrWhiteSpace(accountId)) throw new Exception("accountId is empty."); if (string.IsNullOrWhiteSpace(lightstreamerEndpoint)) throw new Exception("lightstreamerEndpoint is empty."); } catch (Exception x) { throw new Exception("Error parsing account information from login response.", x); } return new(new NadexAPI(client, new NadexAPIAuthenticationData { Username = credentials.Username, JSessionId = jSessionId, XSecurityToken = xSecurityToken, AccountId = accountId, LightstreamerEndpoint = lightstreamerEndpoint }, log)); } catch (Exception x) { client.Dispose(); if (x is OperationCanceledException) { return new(Canceled.Instance); } return new(new Fail { Exception = x, HttpStatusCode = response?.StatusCode, ReasonPhrase = response?.ReasonPhrase, ContentHeaders = response?.Content?.Headers, ContentStream = await (response?.Content?.ReadAsStreamAsync(CancellationToken.None) ?? Task.FromResult(null!)), }); } finally { response?.Dispose(); } }