Skip to content

Commit

Permalink
Added: ability to set basic authentication on requests
Browse files Browse the repository at this point in the history
Motivation:

As an HTTP library, async-http-client should have authentication support.

Modifications:

This adds a `setBasicAuth()` method to both `HTTPClientRequest` and
`HTTPClient.Request` and their related unit tests.

Result:

Library users will be able to leverage this method to use
basic authentication on their requests without implementing
this in their own projects.

Signed-off-by: Agam Dua <agam_dua@apple.com>
  • Loading branch information
agamdua committed Oct 21, 2024
1 parent 0a9b723 commit 19ea3d9
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 0 deletions.
27 changes: 27 additions & 0 deletions Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+auth.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2024 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension HTTPClientRequest {
/// Set basic auth for a request.
///
/// - parameters:
/// - username: the username to authenticate with
/// - password: authentication password associated with the username
public mutating func setBasicAuth(username: String, password: String) {
self.headers.setBasicAuth(username: username, password: password)
}
}
39 changes: 39 additions & 0 deletions Sources/AsyncHTTPClient/BasicAuth.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2024 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation
import NIOHTTP1

/// Generates base64 encoded username + password for http basic auth.
///
/// - Parameters:
/// - username: the username to authenticate with
/// - password: authentication password associated with the username
/// - Returns: encoded credentials to use the Authorization: Basic http header.
func encodeBasicAuthCredentials(username: String, password: String) -> String {
var value = Data()
value.reserveCapacity(username.utf8.count + password.utf8.count + 1)
value.append(contentsOf: username.utf8)
value.append(UInt8(ascii: ":"))
value.append(contentsOf: password.utf8)
return value.base64EncodedString()
}

extension HTTPHeaders {
/// Sets the basic auth header
mutating func setBasicAuth(username: String, password: String) {
let encoded = encodeBasicAuthCredentials(username: username, password: password)
self.replaceOrAdd(name: "Authorization", value: "Basic \(encoded)")
}
}
9 changes: 9 additions & 0 deletions Sources/AsyncHTTPClient/HTTPHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,15 @@ extension HTTPClient {

return (head, metadata)
}

/// Set basic auth for a request.
///
/// - parameters:
/// - username: the username to authenticate with
/// - password: authentication password associated with the username
public mutating func setBasicAuth(username: String, password: String) {
self.headers.setBasicAuth(username: username, password: password)
}
}

/// Represents an HTTP response.
Expand Down
11 changes: 11 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTPClientRequestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ class HTTPClientRequestTests: XCTestCase {
}
}

func testBasicAuth() {
XCTAsyncTest {
var request = Request(url: "https://example.com/get")
request.setBasicAuth(username: "foo", password: "bar")
var preparedRequest: PreparedRequest?
XCTAssertNoThrow(preparedRequest = try PreparedRequest(request))
guard let preparedRequest = preparedRequest else { return }
XCTAssertEqual(preparedRequest.head.headers.first(name: "Authorization")!, "Basic Zm9vOmJhcg==")
}
}

func testUnixScheme() {
XCTAsyncTest {
var request = Request(url: "unix://%2Fexample%2Ffolder.sock/some_path")
Expand Down
7 changes: 7 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTPClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3668,4 +3668,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
let response3 = try await client.execute(request, timeout: /* infinity */ .hours(99))
XCTAssertEqual(.ok, response3.status)
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func testRequestBasicAuth() async throws {
var request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix)
request.setBasicAuth(username: "foo", password: "bar")
XCTAssertEqual(request.headers.first(name: "Authorization"), "Basic Zm9vOmJhcg==")
}
}

0 comments on commit 19ea3d9

Please sign in to comment.