Skip to content

Commit 1e99a96

Browse files
committed
client config: make state parameter optional
The state parameter is recommended but not mandatory so make this configurable in case the server does not support it. By default this is set to true in order to prevent cross-site request forgery. There is a pending PR in the main repo p2#284
1 parent e81b4bc commit 1e99a96

File tree

3 files changed

+37
-7
lines changed

3 files changed

+37
-7
lines changed

Sources/Base/OAuth2Base.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -417,12 +417,15 @@ open class OAuth2Base: OAuth2Securable {
417417
This method checks `state`, throws `OAuth2Error.missingState` or `OAuth2Error.invalidState`. Resets state if it matches.
418418
*/
419419
public final func assureMatchesState(_ params: OAuth2JSON) throws {
420-
guard let state = params["state"] as? String, !state.isEmpty else {
421-
throw OAuth2Error.missingState
422-
}
423-
logger?.trace("OAuth2", msg: "Checking state, got “\(state)”, expecting “\(context.state)")
424-
if !context.matchesState(state) {
425-
throw OAuth2Error.invalidState
420+
if let state = params["state"] as? String, !state.isEmpty {
421+
logger?.trace("OAuth2", msg: "Checking state, got “\(state)”, expecting “\(context.state)")
422+
if !context.matchesState(state) {
423+
throw OAuth2Error.invalidState
424+
}
425+
} else {
426+
if !clientConfig.stateParameterOptional {
427+
throw OAuth2Error.missingState
428+
}
426429
}
427430
context.resetState()
428431
}

Sources/Base/OAuth2ClientConfig.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ open class OAuth2ClientConfig {
7171

7272
/// Add custom parameters to the authorization request.
7373
public var customParameters: [String: String]? = nil
74+
75+
/// Whether the state parameter is optional
76+
public var stateParameterOptional = false
7477

7578
/// Most servers use UTF-8 encoding for Authorization headers, but that's not 100% true: make it configurable (see https://github.com/p2/OAuth2/issues/165).
7679
open var authStringEncoding = String.Encoding.utf8
@@ -134,7 +137,9 @@ open class OAuth2ClientConfig {
134137
if let params = settings["parameters"] as? OAuth2StringDict {
135138
customParameters = params
136139
}
137-
140+
if let stateOptional = settings["state_parameter_optional"] as? Bool {
141+
stateParameterOptional = stateOptional
142+
}
138143
// access token options
139144
if let assume = settings["token_assume_unexpired"] as? Bool {
140145
accessTokenAssumeUnexpired = assume

Tests/FlowTests/OAuth2CodeGrantTests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,28 @@ class OAuth2CodeGrantTests: XCTestCase {
179179
XCTAssertTrue(false, "Should not throw, but threw \(error)")
180180
}
181181
}
182+
183+
func testRedirectURINoStateParameterAllowed() {
184+
let settings: OAuth2JSON = [
185+
"client_id": "abc",
186+
"client_secret": "xyz",
187+
"authorize_uri": "https://auth.ful.io",
188+
"token_uri": "https://token.ful.io",
189+
"keychain": false,
190+
"state_parameter_optional": true
191+
]
192+
let oauth = OAuth2CodeGrant(settings: settings)
193+
oauth.redirect = "oauth2://callback"
194+
oauth.context.redirectURL = oauth.redirect
195+
// parse no state
196+
let redirect = URL(string: "oauth2://callback?code=C0D3")!
197+
do {
198+
_ = try oauth.validateRedirectURL(redirect)
199+
}
200+
catch let error {
201+
XCTAssertTrue(false, "Must not end up here with \(error)")
202+
}
203+
}
182204

183205
func testTokenRequest() {
184206
let oauth = OAuth2CodeGrant(settings: [

0 commit comments

Comments
 (0)