Swiftpack.co -  andybezaire/Authorization as Swift Package
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
andybezaire/Authorization
Authorization appropriate for oauth2 or some other service with tokens and refreshes.
.package(url: "https://github.com/andybezaire/Authorization.git", from: "1.4.0")

Authorization

Twitter: @andy_bezaire

A small module backed by Combine. Used for authorization suitable for oauth 3 legged authorization.

Usage

Create an authorization object:

let auth = Auth(
    doGetTokens: { /* your implementation here */ },
    doRefreshToken: { refresh in /* your implementation here */ }
)

Sign in:

let signingIn = auth.signIn()
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            print("\(error.localizedDescription)")
        case .finished:
            print("Signed in.")
        } 
    })

Do a fetch and Authorization will sign your URLRequest:

let request = URLRequest(url: URL(string: "example.com")!) // provide some URLRequest
let fetching = auth.fetch(request)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            print("\(error.localizedDescription)")
        case .finished:
            print("Request finished.")
        }
    }, receiveValue: { data, response in
        print("Data: \(data), Response: \(response)")
    })

Provide a custom method to sign the request. The default is to use bearer header:

let signRequest: (_ forRequest: URLRequest, _ withToken: Auth.Token) -> URLRequest = { request, token in
    var signedRequest = request
    signedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    return signedRequest
}

Provide a custom method to determine when a refresh is needed. The default is to refresh when response status code is 403:

let shouldDoRefreshFor: (_ forResult: Auth.URLResult) -> Bool = { result in
    if let httpResponse = result.response as? HTTPURLResponse,
       httpResponse.statusCode == 403
    {
        return true
    } else {
        return false
    }
}

Provide a Logger to log to:

import os.log

let logger = Logger(subsystem: "com.example.name", category: "auth")

Create a customized authorization object:

let auth = Auth(
    doGetTokens: { /* your implementation here */ },
    doRefreshToken: { refresh in /* your implementation here */ },
    signRequest: signRequest,
    shouldDoRefreshFor: shouldDoRefreshFor,
    logger: logger
)

Example Code

Here is an example of using Authorization to fetch from "www.example.com/name". This is a non-working example. Replace the Just publishers with the appropriate functions to fetch your tokens and handle a refresh.

import Authorization
import Combine
import Foundation

extension ContentView {
    class Model: ObservableObject {
        lazy var auth: Auth = .init(doGetTokens: doGetTokens, doRefreshToken: doRefreshToken)
        
        func doGetTokens() -> AnyPublisher<Auth.Tokens, Error> {
            return Just(Auth.Tokens(token: "TOKEN", refresh: "REFRESH"))
                .setFailureType(to: Error.self)
                .eraseToAnyPublisher()
        }
        
        func doRefreshToken(refresh: Auth.Refresh) -> AnyPublisher<Auth.Tokens, Error> {
            return Just(Auth.Tokens(token: "TOKEN", refresh: "REFRESH"))
                .setFailureType(to: Error.self)
                .eraseToAnyPublisher()
        }
        
        var signingInOrOut: AnyCancellable?
        func signIn() {
            signingInOrOut = auth.signIn()
                .sink(receiveCompletion: { [unowned self] completion in
                    switch completion {
                    case .failure(let error):
                        print("\(error.localizedDescription)")
                    case .finished:
                        refreshName()
                    }
                })
        }
        
        func signOut() {
            signingInOrOut = auth.signOut()
                .receive(on: RunLoop.main)
                .sink(receiveCompletion: { [unowned self] completion in
                    switch completion {
                    case .failure(let error):
                        print("\(error.localizedDescription)")
                    case .finished:
                        name = nil
                    }
                })
        }
        
        @Published var name: String?
        
        func refreshName() {
            let nameURL = URL(string: "www.example.com/name")!
            let getName = URLRequest(url: nameURL)
            auth.fetch(getName)
                .map(\.data)
                .decode(type: String.self, decoder: JSONDecoder())
                .map { $0 as String? }
                .replaceError(with: nil)
                .receive(on: RunLoop.main)
                .assign(to: &$name)
        }
    }
}

Installation

Swift Package Manager

Add the following dependency to your Package.swift file:

.package(name: "Authorization", url: "https://github.com/andybezaire/Authorization.git", from: "1.0.0")

License

"Authorization" is available under the MIT license. See the LICENSE file for more info.

Credit

Copyright (c) 2021 andybezaire

Created by: Andy Bezaire

GitHub

link
Stars: 1
Last commit: 5 weeks ago

Ad: Job Offers

iOS Software Engineer @ Perry Street Software
Perry Street Software is Jack’d and SCRUFF. We are two of the world’s largest gay, bi, trans and queer social dating apps on iOS and Android. Our brands reach more than 20 million members worldwide so members can connect, meet and express themselves on a platform that prioritizes privacy and security. We invest heavily into SwiftUI and using Swift Packages to modularize the codebase.

Release Notes

v1.4.0
17 weeks ago

Added

  • added force token refresh intended for debug use
  • added token refresh on 401 response

Changed

  • on token refresh, if the new refresh is nil, then keep the old refresh
  • using refresh on 401 as default
  • cleaned up logging

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API