Skip to content

Unable to Capture SID Cookie from Session Token Flow (ClassicNativeAuth) #270

@shankar12

Description

@shankar12

Describe the bug?

Okta Mobile Swift SDK prevents application code from accessing SID cookie received via HTTP Redirect 302 from /authorize endpoint due to SDK architectural design (private URL session / cookie handling).

Based on SDK source code review,the SDK's SessionTokenFlowExchange actor uses URLSessionConfiguration.ephemeral (SessionTokenFlow.swift, lines 215-218), which creates isolated in-memory cookie storage that is not accessible via HTTPCookieStorage.shared.

// Current implementation in SessionTokenFlowExchange
private lazy var session: any URLSessionProtocol = {
    URLSession(configuration: .ephemeral,  // ← Isolates cookies
               delegate: self,
               delegateQueue: nil)
}()

What is expected to happen?

We should have access to SID and other cookies received via /authorize endpoint in application code.

What is the actual behavior?

SID is successfully returned by OKTA from /authorize endpoint response (HTTP 302 Redirect) for session token flow. However, we do not have access to this cookie in HTTPCookieStorage.shared property.

Reproduction Steps?

  1. Run the ClassicNativeAuth Sample from the following link:
    https://github.com/okta/okta-mobile-swift/tree/master/Samples/ClassicNativeAuth
  2. Setup Okta configuration in OKTA.PLIST
  3. Login with Sample credentials
  4. Observe the /authorize endpoint
  5. It returns an HTTP Redirect 302 with all necessary cookies for the calling application.
  6. Observe that SID is unavailable in HTTPCookie.shared storage.

Additional Information?

Proposed Fix at SDK Level:

Public API Approach (Recommended - No Internal Exposure):

// 1. Add configuration parameter to public SessionTokenFlow initializer
public init(client: OAuth2Client,
            sessionConfiguration: URLSessionConfiguration? = nil,  // ← New parameter
            additionalParameters: [String: any APIRequestArgument]? = nil) throws {
    self.client = client
    self.sessionConfiguration = sessionConfiguration
    self.additionalParameters = additionalParameters
    client.add(delegate: self)
}

// 2. Store configuration in SessionTokenFlow
private let sessionConfiguration: URLSessionConfiguration?

// 3. Update protocol to accept configuration
protocol SessionTokenFlowURLExchange: Sendable {
    init(scheme: String, configuration: URLSessionConfiguration?)
    func follow(url: URL) async throws -> URL
}

// 4. Pass configuration when creating exchange
private func complete(using flow: AuthorizationCodeFlow, url: URL) async throws -> Token {
    let follow = SessionTokenFlow.urlExchangeClass.init(
        scheme: scheme,
        configuration: sessionConfiguration  // ← Pass through
    )
    let url = try await follow.follow(url: url)
    return try await flow.resume(with: url)
}

// 5. Use configuration in internal SessionTokenFlowExchange
actor SessionTokenFlowExchange {
    private let customConfiguration: URLSessionConfiguration?
    
    init(scheme: String, configuration: URLSessionConfiguration? = nil) {
        self.customConfiguration = configuration
    }
    //Pass custom URLSessionConfiguration if cookies need to be accessed.  
    private lazy var session: any URLSessionProtocol = {
        URLSession(configuration: customConfiguration ?? .ephemeral,
                   delegate: self,
                   delegateQueue: nil)
    }()
}

Usage in application code:

// Application provides custom configuration through public API
let config = URLSessionConfiguration.default
config.httpCookieStorage = .shared
config.httpCookieAcceptPolicy = .always

let flow = try SessionTokenFlow(
    client: client,
    sessionConfiguration: config  // ← Cookie capture enabled
)

This approach:

  • Uses public API only (SessionTokenFlow initializer)
  • It does not override the private URLSession used for handling HTTP 302 Redirect
  • Just provides a way to inject URLSessionConfiguration to access cookies.
  • No internal class exposure required
  • Maintains backward compatibility (defaults to .ephemeral)
  • Clean separation of concerns
  • No breaking changes to existing implementations

SDK Version(s)

2.1.4

Build Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions