private bool Parse_FirstLeg_Authentication_Content( string _ResponseContent, out string _AuthorizationCode_From_FirstLeg, out BMemoryQueryParameters _SSOStateUniqueID_QueryParameters, out SSOStateMEntry _SSOState, out string _LocalRedirectUrl_From_FirstLeg, out string _EmailAddress_From_FirstLeg, out string _AzureADUniqueID_From_FirstLeg, out BWebServiceResponse _FailureResponse, Action <string> _ErrorMessageAction) { _AuthorizationCode_From_FirstLeg = null; _SSOStateUniqueID_QueryParameters = new BMemoryQueryParameters(); _SSOState = null; _LocalRedirectUrl_From_FirstLeg = null; _EmailAddress_From_FirstLeg = null; _AzureADUniqueID_From_FirstLeg = null; _FailureResponse = BWebResponse.InternalError(""); _ResponseContent = _ResponseContent.Trim(); //Handle error if (_ResponseContent.StartsWith("error=")) { var ErrorResponse = new JObject() { ["result"] = "failure" }; try { var ErrorFields = _ResponseContent.Split('&'); if (ErrorFields != null && ErrorFields.Length >= 2) { ErrorResponse["error"] = ErrorFields[0].Substring("error=".Length); ErrorResponse["message"] = ErrorFields[1].Substring("error_description=".Length); } } catch (Exception) { } _FailureResponse = BWebResponse.Unauthorized(ErrorResponse.ToString()); return(false); } //Normal flow var Splitted = _ResponseContent.Split('&'); if (Splitted == null || Splitted.Length < 3) { _FailureResponse = BWebResponse.BadRequest("Request body must contain all requested types. Split has failed."); return(false); } string IDToken = null; string StateField = null; for (var i = 0; i < Splitted.Length; i++) { if (Splitted[i].StartsWith("id_token=")) { IDToken = Splitted[i].Substring("id_token=".Length); } else if (Splitted[i].StartsWith("code=")) { _AuthorizationCode_From_FirstLeg = Splitted[i].Substring("code=".Length); } else if (Splitted[i].StartsWith("state=")) { StateField = WebUtility.UrlDecode(Splitted[i].Substring("state=".Length)); } } if (IDToken == null || _AuthorizationCode_From_FirstLeg == null || StateField == null) { _FailureResponse = BWebResponse.BadRequest("Request body must contain all requested types."); return(false); } Splitted = StateField.Split('&'); if (Splitted == null || Splitted.Length < 3) { _FailureResponse = BWebResponse.BadRequest("State field must contain all mandatory entries. Split has failed."); return(false); } bool bSSOStateUniqueID_QueryParameters_Set = false; string TenantName = null; for (var i = 0; i < Splitted.Length; i++) { if (Splitted[i].StartsWith("redirect_url=")) { _LocalRedirectUrl_From_FirstLeg = WebUtility.UrlDecode(Splitted[i].Substring("redirect_url=".Length)); } else if (Splitted[i].StartsWith("tenant=")) { TenantName = Splitted[i].Substring("tenant=".Length); } else if (Splitted[i].StartsWith("state=")) { _SSOStateUniqueID_QueryParameters = SSOStateMEntry.ID_SSO_STATE_MEMORY_SERVICE_KEY(Splitted[i].Substring("state=".Length)); bSSOStateUniqueID_QueryParameters_Set = true; } } if (_LocalRedirectUrl_From_FirstLeg == null || TenantName == null || !bSSOStateUniqueID_QueryParameters_Set) { _FailureResponse = BWebResponse.BadRequest("State field must contain all mandatory entries."); return(false); } var Serialized = MemoryService.GetKeyValue(_SSOStateUniqueID_QueryParameters, SSOStateMEntry.HASH_KEY, _ErrorMessageAction); if (Serialized == null) { _FailureResponse = BWebResponse.Unauthorized("Login prompt session has expired. Please try again."); return(false); } try { _SSOState = JsonConvert.DeserializeObject <SSOStateMEntry>(Serialized.AsString); if (_SSOState == null) { throw new NullReferenceException(); } } catch (Exception e) { _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Parse_FirstLeg_Authentication_Content: Invalid session state. Message: " + e.Message + ", trace: " + e.StackTrace); _FailureResponse = BWebResponse.InternalError("Invalid session state. Please try again."); return(false); } if (_SSOState.Status != SSOStateMEntry.STATUS_AUTHENTICATING) { _FailureResponse = BWebResponse.Unauthorized("Invalid SSO state. Please try again."); return(false); } if (TenantName != _SSOState.TenantName) { _FailureResponse = BWebResponse.Unauthorized("SSO state - request tenant mismatch. Please try again."); return(false); } var JWTHandler = new JwtSecurityTokenHandler(); JwtSecurityToken Token = null; try { Token = JWTHandler.ReadJwtToken(IDToken); } catch (Exception e) { _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Parse_FirstLeg_Authentication_Content: Invalid JWT token. Token: " + IDToken + ", message: " + e.Message + ", trace: " + e.StackTrace); _FailureResponse = BWebResponse.BadRequest("Invalid JWT token."); return(false); } if (!Token.Payload.TryGetValue("email", out object EmailObject)) { _FailureResponse = BWebResponse.BadRequest("JWT token does not contain email in the payload."); return(false); } _EmailAddress_From_FirstLeg = ((string)EmailObject).ToLower(); if (!Token.Payload.TryGetValue("sub", out object AzureADUserUniqueIDObject)) { _FailureResponse = BWebResponse.BadRequest("JWT token does not contain sub in the payload."); return(false); } _AzureADUniqueID_From_FirstLeg = ((string)AzureADUserUniqueIDObject).ToLower(); return(true); }
private bool Perform_SecondLeg_Authorization( string _AuthorizationCode_From_FirstLeg, BMemoryQueryParameters _SSOStateUniqueID_QueryParameters, SSOStateMEntry _SSOState, out AuthorizationResult _SuccessResponse, out BWebServiceResponse _FailureResponse, Action <string> _ErrorMessageAction) { _SuccessResponse = null; _FailureResponse = BWebResponse.InternalError(""); _SSOState.Status = SSOStateMEntry.STATUS_AUTHORIZING; MemoryService.SetKeyValue(_SSOStateUniqueID_QueryParameters, new Tuple <string, BPrimitiveType>[] { new Tuple <string, BPrimitiveType>(SSOStateMEntry.HASH_KEY, new BPrimitiveType(JsonConvert.SerializeObject(_SSOState))) }, _ErrorMessageAction); MemoryService.SetKeyExpireTime(_SSOStateUniqueID_QueryParameters, TimeSpan.FromSeconds(120), _ErrorMessageAction); var FormUrlEncodedPairs = new List <KeyValuePair <string, string> >() { new KeyValuePair <string, string>("client_id", AzureAD_AppID), new KeyValuePair <string, string>("scope", SSOCommon.SCOPE), new KeyValuePair <string, string>("grant_type", "authorization_code"), new KeyValuePair <string, string>("code", _AuthorizationCode_From_FirstLeg), new KeyValuePair <string, string>("redirect_uri", WebUtility.UrlDecode(_SSOState.ServersideRedirectUrl)), new KeyValuePair <string, string>("client_secret", WebUtility.UrlDecode(AzureAD_ClientSecret)) }; using var Handler = new HttpClientHandler { SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, ServerCertificateCustomValidationCallback = (a, b, c, d) => true }; using var Client = new HttpClient(Handler); Client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); string ResponseString = null; try { using var RequestContent = new FormUrlEncodedContent(FormUrlEncodedPairs); using var RequestTask = Client.PostAsync("https://login.microsoftonline.com/common/oauth2/v2.0/token", RequestContent); RequestTask.Wait(); using var Response = RequestTask.Result; using var ResponseContent = Response.Content; using var ReadResponseTask = ResponseContent.ReadAsStringAsync(); ReadResponseTask.Wait(); ResponseString = ReadResponseTask.Result; if (!Response.IsSuccessStatusCode) { bool bJsonParseable = true; try { JObject.Parse(ResponseString); } catch (JsonReaderException) { bJsonParseable = false; } _FailureResponse = new BWebServiceResponse( (int)Response.StatusCode, new BStringOrStream(ResponseString), bJsonParseable ? "application/json" : "text/html"); return(false); } var Parsed = JObject.Parse(ResponseString); _SuccessResponse = new AuthorizationResult() { TokenType = (string)Parsed["token_type"], AccessToken = (string)Parsed["access_token"], ExpiresInSeconds = (int)Parsed["expires_in"], RefreshToken = (string)Parsed["refresh_token"] }; } catch (Exception e) { if (e.InnerException != null && e.InnerException != e) { _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Perform_SecondLeg_Authorization->Inner: " + e.InnerException.Message + ", Trace: " + e.InnerException.StackTrace); } if (e is AggregateException) { foreach (var Inner in (e as AggregateException).InnerExceptions) { _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Perform_SecondLeg_Authorization->Aggregate->Inner: " + Inner.Message + ", Trace: " + Inner.StackTrace); } } _ErrorMessageAction?.Invoke("Error: SSOLoginCallback->Perform_SecondLeg_Authorization: Authorization request failed. Response: " + ResponseString + ", message: " + e.Message + ", trace: " + e.StackTrace); _FailureResponse = BWebResponse.InternalError("Authorization request has failed."); return(false); } MemoryService.DeleteAllKeys(_SSOStateUniqueID_QueryParameters, true, _ErrorMessageAction); return(true); }
private BWebServiceResponse OnRequest_Internal(HttpListenerContext _Context, Action <string> _ErrorMessageAction = null) { if (!UrlParameters.TryGetValue("redirect_url", out string RedirectUrlEncoded) || RedirectUrlEncoded.Length == 0) { RedirectUrlEncoded = DEFAULT_REDIRECT_URL_ENCODED; } if (_Context.Request.HttpMethod != "GET") { _ErrorMessageAction?.Invoke("SSOLoginRequest: GET method is accepted. But received request method: " + _Context.Request.HttpMethod); return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, BWebResponse.Error_BadRequest_Code, "GET method is accepted. But received request method: " + _Context.Request.HttpMethod)); } if (!UrlParameters.TryGetValue("tenant", out string TenantName) || TenantName.Length == 0) { TenantName = DEFAULT_TENANT_NAME; } else { TenantName = TenantName.ToLower(); } //Existing token from header string ClientAuthorization = null; if (BWebUtilities.DoesContextContainHeader(out List <string> ClientAuthorizationHeaderValues, out string _, _Context, "client-authorization")) { BUtility.CheckAndGetFirstStringFromList(ClientAuthorizationHeaderValues, out ClientAuthorization); if (ClientAuthorization != null && ClientAuthorization.Length == 0) { ClientAuthorization = null; } } //Existing token from url parameters //Note: Must be token type prepended. Example: ?existing_token=bearer%20abc123123 if (!UrlParameters.TryGetValue("existing_token", out string ExistingToken) || ExistingToken.Length == 0) { ExistingToken = null; } else { ExistingToken = WebUtility.UrlDecode(ExistingToken); } //If both existing tokens are non-null; it is error if (ClientAuthorization != null && ExistingToken != null) { _ErrorMessageAction?.Invoke("Error: SSOLoginRequest: Both existing tokens from url parameters and headers are non-null."); return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, BWebResponse.Error_BadRequest_Code, "Both existing tokens from url parameters and headers are non-null.")); } //From now on, use ClientAuthorization; not ExistingToken if (ExistingToken != null) { ClientAuthorization = ExistingToken; } //Check and try refresh if expired if (ClientAuthorization != null && new Controller_SSOAccessToken(ClientAuthorization, DatabaseService, MemoryService, AzureAD_AppID, AzureAD_ClientSecret, SSOSuperAdmins, _ErrorMessageAction) .PerformCheckAndRefresh( out Controller_SSOAccessToken.EPerformCheckAndRefreshSuccessStatus _, out ClientAuthorization, out string UserID, out string _)) { return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), false, 0, null, UserID, ClientAuthorization)); } //Get api passthrough endpoint from internal set state var LocalErrorString = ""; if (!InternalSetState.GetValueFromMemoryService( out string ApiPassthroughEndpoint, InternalSetState.API_PASSTHROUGH_PUBLIC_ENDPOINT_PROPERTY, MemoryService, (string _Message) => { LocalErrorString = _Message; _ErrorMessageAction?.Invoke(_Message); })) { return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, 500, LocalErrorString)); } string ServersideRedirectUrl = WebUtility.UrlEncode(ApiPassthroughEndpoint + "/auth/login/azure/callback"); string AzureAuthenticationEndpointBase = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize" + "?client_id=" + AzureAD_AppID + "&response_type=id_token code" + "&redirect_uri=" + ServersideRedirectUrl; var TrialCount = 0; string SSOStateUniqueID; BMemoryQueryParameters SSOStateUniqueID_QueryParameters; do { if (!BUtility.CalculateStringMD5(BUtility.RandomString(32, true), out SSOStateUniqueID, _ErrorMessageAction)) { return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, 500, "SSO State ID generation has failed.")); } SSOStateUniqueID_QueryParameters = SSOStateMEntry.ID_SSO_STATE_MEMORY_SERVICE_KEY(SSOStateUniqueID); if (!MemoryService.SetKeyValueConditionally( SSOStateUniqueID_QueryParameters, new Tuple <string, BPrimitiveType>( SSOStateMEntry.HASH_KEY, new BPrimitiveType(JsonConvert.SerializeObject( new SSOStateMEntry() { ServersideRedirectUrl = ServersideRedirectUrl, TenantName = TenantName, Status = SSOStateMEntry.STATUS_AUTHENTICATING }) ) ), _ErrorMessageAction)) { SSOStateUniqueID = null; } } while (SSOStateUniqueID == null && ++TrialCount < 5); if (SSOStateUniqueID == null) { return(SSOCommon.MakeCallerRedirected(WebUtility.UrlDecode(RedirectUrlEncoded), true, 500, "Unique SSO State ID generation has failed.")); } MemoryService.SetKeyExpireTime(SSOStateUniqueID_QueryParameters, TimeSpan.FromSeconds(120), _ErrorMessageAction); var AzureAuthenticationEndpoint = AzureAuthenticationEndpointBase + "&scope=" + SSOCommon.SCOPE_URL_ENCODED + "&response_mode=form_post" + "&nonce=" + SSOStateUniqueID + "&state=" + WebUtility.UrlEncode( "redirect_url=" + RedirectUrlEncoded + "&tenant=" + TenantName + "&state=" + SSOStateUniqueID); return(SSOCommon.MakeCallerRedirected(AzureAuthenticationEndpoint, false, 0, null)); }