You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+107-2Lines changed: 107 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -687,7 +687,7 @@ advanced usage scenarios:
687
687
- Specifying separate SP certificates for signing and encryption.
688
688
689
689
The `sp_cert_multi` parameter replaces `certificate` and `private_key`
690
-
(you may not specify both pparameters at the same time.) `sp_cert_multi` has the following shape:
690
+
(you may not specify both parameters at the same time.) `sp_cert_multi` has the following shape:
691
691
692
692
```ruby
693
693
settings.sp_cert_multi = {
@@ -910,7 +910,7 @@ end
910
910
911
911
### Attribute Service
912
912
913
-
To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion.
913
+
To request attributes from the IdP the SP needs to provide an attribute service within its metadata and reference the index in the assertion.
914
914
915
915
```ruby
916
916
settings =RubySaml::Settings.new
@@ -964,6 +964,111 @@ end
964
964
MyMetadata.new.generate(settings)
965
965
```
966
966
967
+
### Preventing Replay Attacks
968
+
969
+
A replay attack is when an attacker intercepts a valid SAML assertion and "replays" it at a later time to gain unauthorized access.
970
+
971
+
The library only checks the assertion's validity window (`NotBefore` and `NotOnOrAfter` conditions). An attacker can replay a valid assertion as many times as they want within this window.
972
+
973
+
A robust defense requires tracking of assertion IDs to ensure any given assertion is only accepted once.
974
+
975
+
#### 1. Extract the Assertion ID after Validation
976
+
977
+
After a response has been successfully validated, get the assertion ID. The library makes this available via `response.assertion_id`.
978
+
979
+
980
+
#### 2. Store the ID with an Expiry
981
+
982
+
You must store this ID in a persistent cache (like Redis or Memcached) that is shared across your servers. Do not store it in the user's session, as that is not a secure cache.
983
+
984
+
The ID should be stored until the assertion's validity window has passed. You will need to check how long the trusted IdPs consider the assertion valid and then add the allowed_clock_drift.
985
+
986
+
You can define a global value, or set this value dinamically based on the `not_on_or_after` value of the re + `allowed_clock_drift`.
987
+
988
+
```ruby
989
+
# In your `consume` action, after a successful validation:
990
+
if response.is_valid?
991
+
# Prevent replay of this specific assertion
992
+
assertion_id = response.assertion_id
993
+
authorize_failure("Assertion ID is mandatory") if assertion_id.nil?
### Enforce SP-Initiated Flow with `InResponseTo` validation
1031
+
1032
+
This is the best way to prevent IdP-initiated logins and ensure that you only accept assertions that you recently requested.
1033
+
1034
+
#### 1. Store the `AuthnRequest` ID
1035
+
1036
+
When you create an `AuthnRequest`, the library assigns it a unique ID. You must store this ID, for example in the user's session *before* redirecting them to the IdP.
1037
+
1038
+
```ruby
1039
+
definit
1040
+
request =OneLogin::RubySaml::Authrequest.new
1041
+
# The unique ID of the request is in request.uuid
1042
+
session[:saml_request_id] = request.uuid
1043
+
redirect_to(request.create(saml_settings))
1044
+
end
1045
+
```
1046
+
1047
+
#### 2. Validate the `InResponseTo` value of the `Response` with the Stored ID
1048
+
1049
+
When you process the `SAMLResponse`, retrieve the ID from the session and pass it to the `Response` constructor. Use `session.delete` to ensure the ID can only be used once.
1050
+
1051
+
```ruby
1052
+
defconsume
1053
+
request_id = session.delete(:saml_request_id) # Use delete to prevent re-use
1054
+
1055
+
# You can reject the response if no previous saml_request_id was stored
1056
+
raise"IdP-initiaited detected"if request_id.nil?
1057
+
1058
+
response =OneLogin::RubySaml::Response.new(
1059
+
params[:SAMLResponse],
1060
+
settings: saml_settings,
1061
+
matches_request_id: request_id
1062
+
)
1063
+
1064
+
if response.is_valid?
1065
+
# ... authorize user
1066
+
else
1067
+
# Response is invalid, errors in response.errors
1068
+
end
1069
+
end
1070
+
```
1071
+
967
1072
## Contributing
968
1073
969
1074
### Pay it Forward: Support RubySAML and Strengthen Open-Source Security
0 commit comments