public async Task <ActionResult> Generate() { string input = null; // If not data came in, then return if (this.Request.Body == null) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent("Request content is null", HttpStatusCode.Conflict))); } // Read the input claims from the request body using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { input = await reader.ReadToEndAsync(); } // Check input content value if (string.IsNullOrEmpty(input)) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent("Request content is empty", HttpStatusCode.Conflict))); } // Convert the input string into InputClaimsModel object TotpInputClaims inputClaims = TotpInputClaims.Parse(input); if (inputClaims == null) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent("Can not deserialize input claims", HttpStatusCode.Conflict))); } try { // Define the URL for the QR code. When user scan this URL, it opens one of the // authentication apps running on the mobile device byte[] secretKey = KeyGeneration.GenerateRandomKey(20); string TOTPUrl = KeyUrl.GetTotpUrl(secretKey, $"{AppSettings.TOTPAccountPrefix}:{inputClaims.UserName}", AppSettings.TOTPTimestep); TOTPUrl = $"{TOTPUrl}&issuer={AppSettings.TOTPIssuer.Replace(" ", "%20")}"; // Generate QR code for the above URL var qrCodeGenerator = new QRCodeGenerator(); QRCodeData qrCodeData = qrCodeGenerator.CreateQrCode(TOTPUrl, QRCodeGenerator.ECCLevel.L); BitmapByteQRCode qrCode = new BitmapByteQRCode(qrCodeData); byte[] qrCodeBitmap = qrCode.GetGraphic(4); var output = new B2CResponseContent(string.Empty, HttpStatusCode.OK) { QrCodeBitmap = Convert.ToBase64String(qrCodeBitmap), SecretKey = this.EncryptAndBase64(Convert.ToBase64String(secretKey)) }; return(Ok(output)); } catch (Exception ex) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent($"General error (REST API): {ex.Message}", HttpStatusCode.Conflict))); } }
public async Task <ActionResult> Verify() { string input = null; // If not data came in, then return if (this.Request.Body == null) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent("Request content is null", HttpStatusCode.Conflict))); } // Read the input claims from the request body using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { input = await reader.ReadToEndAsync(); } // Check input content value if (string.IsNullOrEmpty(input)) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent("Request content is empty", HttpStatusCode.Conflict))); } // Convert the input string into InputClaimsModel object var inputClaims = TotpInputClaims.Parse(input); if (inputClaims == null) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent("Can not deserialize input claims", HttpStatusCode.Conflict))); } try { byte[] secretKey = Convert.FromBase64String(this.DecryptAndBase64(inputClaims.SecretKey)); Totp totp = new Totp(secretKey); long timeStepMatched; // Verify the TOTP code provided by the users bool verificationResult = totp.VerifyTotp( inputClaims.TotpCode, out timeStepMatched, VerificationWindow.RfcSpecifiedNetworkDelay); if (!verificationResult) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent("The verification code is invalid.", HttpStatusCode.Conflict))); } // Using the input claim 'timeStepMatched', we check whether the verification code has already been used. // For sign-up, the 'timeStepMatched' input claim is null and should not be evaluated // For sign-in, the 'timeStepMatched' input claim contains the last time last matched (from the user profile), and evaluated with // the value of the result of the TOTP out 'timeStepMatched' variable if (!string.IsNullOrEmpty(inputClaims.TimeStepMatched) && (inputClaims.TimeStepMatched).Equals(timeStepMatched.ToString())) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent("The verification code has already been used.", HttpStatusCode.Conflict))); } var output = new B2CResponseContent(string.Empty, HttpStatusCode.OK) { TimeStepMatched = timeStepMatched.ToString() }; return(Ok(output)); } catch (Exception ex) { return(StatusCode((int)HttpStatusCode.Conflict, new B2CResponseContent($"General error (REST API): {ex.Message}", HttpStatusCode.Conflict))); } }