// Returns the second factor token from Duo or null when canceled by the user. public static Result Authenticate(string host, string signature, IDuoUi ui, IRestTransport transport) { var rest = new RestClient(transport, $"https://{host}"); var(tx, app) = ParseSignature(signature); var html = DownloadFrame(tx, rest); var(sid, devices) = ParseFrame(html); while (true) { // Ask the user to choose what to do var choice = ui.ChooseDuoFactor(devices); if (choice == null) { return(null); // Canceled by user } // SMS is a special case: it doesn't submit any codes, it rather tells the server to send // a new batch of passcodes to the phone via SMS. if (choice.Factor == DuoFactor.SendPasscodesBySms) { SubmitFactor(sid, choice, "", rest); choice = new DuoChoice(choice.Device, DuoFactor.Passcode, choice.RememberMe); } // Ask for the passcode var passcode = ""; if (choice.Factor == DuoFactor.Passcode) { passcode = ui.ProvideDuoPasscode(choice.Device); if (passcode.IsNullOrEmpty()) { return(null); // Canceled by user } } var token = SubmitFactorAndWaitForToken(sid, choice, passcode, ui, rest); // Flow error like an incorrect passcode. The UI has been updated with the error. Keep going. if (token.IsNullOrEmpty()) { continue; } // All good return(new Result($"{token}:{app}", choice.RememberMe)); } }