{"id":25388733,"url":"https://github.com/codefiesta/oauthkit","last_synced_at":"2026-01-16T12:21:51.667Z","repository":{"id":242000019,"uuid":"807399388","full_name":"codefiesta/OAuthKit","owner":"codefiesta","description":"A modern and observable framework for OAuth 2.0 authorization flows.","archived":false,"fork":false,"pushed_at":"2026-01-12T18:01:56.000Z","size":1677,"stargazers_count":28,"open_issues_count":2,"forks_count":5,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-12T21:52:12.928Z","etag":null,"topics":["github-api","google-api","ios","linkedin-api","macos","microsoft-azure","oauth","oauth-flow","oauth2","oauth2-authentication","oauth2-client","slack-api","slack-app","spm","swift","swift-package-manager","swiftui","tvos","visionos","xcode26"],"latest_commit_sha":null,"homepage":"https://codefiesta.github.io/OAuthKit","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codefiesta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-05-29T03:10:07.000Z","updated_at":"2026-01-10T12:43:58.000Z","dependencies_parsed_at":"2024-05-31T04:23:15.155Z","dependency_job_id":"3b36fc0f-3369-467c-bb60-a6541f2db46e","html_url":"https://github.com/codefiesta/OAuthKit","commit_stats":null,"previous_names":["codefiesta/oauthkit"],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/codefiesta/OAuthKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefiesta%2FOAuthKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefiesta%2FOAuthKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefiesta%2FOAuthKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefiesta%2FOAuthKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codefiesta","download_url":"https://codeload.github.com/codefiesta/OAuthKit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefiesta%2FOAuthKit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478570,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["github-api","google-api","ios","linkedin-api","macos","microsoft-azure","oauth","oauth-flow","oauth2","oauth2-authentication","oauth2-client","slack-api","slack-app","spm","swift","swift-package-manager","swiftui","tvos","visionos","xcode26"],"created_at":"2025-02-15T13:38:31.240Z","updated_at":"2026-01-16T12:21:51.657Z","avatar_url":"https://github.com/codefiesta.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Build](https://github.com/codefiesta/OAuthKit/actions/workflows/swift.yml/badge.svg)\n![Swift 6.1+](https://img.shields.io/badge/Swift-6.1%2B-gold.svg)\n![Xcode 26.0+](https://img.shields.io/badge/Xcode-26.0%2B-tomato.svg)\n![iOS 17.0+](https://img.shields.io/badge/iOS-17.0%2B-crimson.svg)\n![macOS 15.0+](https://img.shields.io/badge/macOS-15.0%2B-skyblue.svg)\n![tvOS 18.0+](https://img.shields.io/badge/tvOS-18.0%2B-blue.svg)\n![visionOS 1.0+](https://img.shields.io/badge/visionOS-1.0%2B-violet.svg)\n![watchOS 10.0+](https://img.shields.io/badge/watchOS-10.0%2B-magenta.svg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-indigo.svg)](https://opensource.org/licenses/MIT)\n![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/codefiesta/87655b6e3c89b9198287b2fefbfa641f/raw/oauthkit-coverage.json)\n\n# OAuthKit\n\u003cimg src=\"https://github.com/user-attachments/assets/039ee445-42af-433d-9793-56fc36330952\" height=\"100\" align=\"left\"/\u003e\n\nOAuthKit is a contemporary, event-driven Swift Package that utilizes the [Observation](https://developer.apple.com/documentation/observation) Framework to implement the observer design pattern and publish [OAuth 2.0](https://oauth.net/2/) events. This enables application developers to effortlessly configure OAuth Providers and concentrate on developing exceptional applications rather than being preoccupied with the intricacies of authorization flows.\n\u003cbr clear=\"left\"/\u003e\n\n## OAuthKit Features\n\nOAuthKit is a small, lightweight package that provides out of the box [Swift Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/) safety support and [Observable](https://codefiesta.github.io/OAuthKit/documentation/oauthkit/oauth/state-swift.enum) OAuth 2.0 state events that allow fine grained control over when and how to start authorization flows. \n\nKey features include:\n\n- [Simple Configuration](#oauthkit-configuration)\n- [Keychain protection with biometrics or companion device](https://codefiesta.github.io/OAuthKit/documentation/oauthkit/configuration#Keychain-Protection)\n- [Private Browsing with non-persistent WebKit Datastores](https://codefiesta.github.io/OAuthKit/documentation/oauthkit/configuration#Private-Browsing)\n- [Custom URLSession](https://codefiesta.github.io/OAuthKit/documentation/oauthkit/configuration#URL-Session) configuration for complete control custom protocol specific data\n- [Observable State](https://codefiesta.github.io/OAuthKit/documentation/oauthkit/gettingstarted#Observing-OAuth-State) driven events to allow full control over when and if users are prompted to authenticate with an OAuth provider\n- Supports all Apple Platforms (iOS, macOS, tvOS, visionOS, watchOS)\n- [Support for every OAuth 2.0 Flow](#oauthkit-authorization-flows)\n\t- [Authorization Code](#oauth-20-authorization-code-flow)\n\t- [PKCE](#oauth-20-pkce-flow)\n\t- [Device Code](#oauth-20-device-code-flow)\n\t- [Client Credentials](#oauth-20-client-credentials-flow)\n\t- [OpenID Connect](https://www.oauth.com/playground/oidc.html)\n\n## OAuthKit Installation\n\nOAuthKit can be installed using [Swift Package Manager](https://www.swift.org/documentation/package-manager/).\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/codefiesta/OAuthKit\", from: \"2.1.0\")\n]\n```\n\n## OAuthKit Usage\n\nThe following is an example of the simplest usage of using OAuthKit across multiple platforms (iOS, macOS, visionOS, tvOS, watchOS):\n\n```swift\nimport OAuthKit\nimport SwiftUI\n\n@main\nstruct OAuthApp: App {\n\n    @Environment(\\.oauth)\n    var oauth: OAuth\n    \n    /// Build the scene body\n    var body: some Scene {\n\n        WindowGroup {\n            ContentView()\n        }\n        \n        #if canImport(WebKit)\n        WindowGroup(id: \"oauth\") {\n            OAWebView(oauth: oauth)\n        }\n        #endif\n    }\n} \n\nstruct ContentView: View {\n    \n    @Environment(\\.oauth)\n    var oauth: OAuth\n\n    #if canImport(WebKit)\n    @Environment(\\.openWindow)\n    var openWindow\n    \n    @Environment(\\.dismissWindow)\n    private var dismissWindow\n    #endif\n\n    /// The view body that reacts to oauth state changes\n    var body: some View {\n        VStack {\n            switch oauth.state {\n            case .empty:\n                providerList\n            case .authorizing(let provider, let grantType):\n                Text(\"Authorizing [\\(provider.id)] with [\\(grantType.rawValue)]\")\n            case .requestingAccessToken(let provider):\n                Text(\"Requesting Access Token [\\(provider.id)]\")\n            case .requestingDeviceCode(let provider):\n                Text(\"Requesting Device Code [\\(provider.id)]\")\n            case .authorized(let provider, _):\n                Button(\"Authorized [\\(provider.id)]\") {\n                    oauth.clear()\n                }\n            case .receivedDeviceCode(_, let deviceCode):\n                Text(\"To login, visit\")\n                Text(.init(\"[\\(deviceCode.verificationUri)](\\(deviceCode.verificationUri))\"))\n                    .foregroundStyle(.blue)\n                Text(\"and enter the following code:\")\n                Text(deviceCode.userCode)\n                    .padding()\n                    .border(Color.primary)\n                    .font(.title)\n            case .error(let provider, let error):\n                Text(\"Error [\\(provider.id)]: \\(error.localizedDescription)\")\n            }\n        }\n        .onChange(of: oauth.state) { _, state in\n            handle(state: state)\n        }\n    }\n    \n    /// Displays a list of oauth providers.\n    var providerList: some View {\n        List(oauth.providers) { provider in\n            Button(provider.id) {\n                authorize(provider: provider)\n            }\n        }\n    }\n\n    /// Starts the authorization process for the specified provider.\n    /// - Parameter provider: the provider to begin authorization for\n    private func authorize(provider: OAuth.Provider) {\n        #if canImport(WebKit)\n        // Use the PKCE grantType for iOS, macOS, visionOS\n        let grantType: OAuth.GrantType = .pkce(.init())\n        #else\n        // Use the Device Code grantType for tvOS, watchOS\n        let grantType: OAuth.GrantType = .deviceCode\n        #endif\n\n        // Start the authorization flow\n        oauth.authorize(provider: provider, grantType: grantType)\n    }\n    \n    /// Reacts to oauth state changes by opening or closing authorization windows.\n    /// - Parameter state: the published state change\n    private func handle(state: OAuth.State) {\n        #if canImport(WebKit)\n        switch state {\n        case .empty, .error, .requestingAccessToken, .requestingDeviceCode:\n            break\n        case .authorizing, .receivedDeviceCode:\n            openWindow(id: \"oauth\")\n        case .authorized(_, _):\n            dismissWindow(id: \"oauth\")\n        }\n        #endif\n    }\n}\n```\n\n## OAuthKit Configuration\nBy default, the easiest way to configure OAuthKit is to simply drop an `oauth.json` file into your main bundle and it will get automatically loaded into your swift application and available as an [Environment](https://developer.apple.com/documentation/swiftui/environment) property wrapper. You can find an example `oauth.json` file [here](https://github.com/codefiesta/OAuthKit/blob/main/Tests/OAuthKitTests/Resources/oauth.json). OAuthKit provides flexible constructor options that allows developers to customize  how their oauth client is initialized and what features they want to implement. See the [oauth.init(\\_:bundle:options)](https://codefiesta.github.io/OAuthKit/documentation/oauthkit/oauth) method for details.\n\n### OAuth initialized from main bundle (default)\n```swift\n@Environment(\\.oauth)\nvar oauth: OAuth\n```\n### OAuth initialized from specified bundle\nIf you want to customize your OAuth environment or are using modules in your application, you can also specify which bundle to load configure files from:\n\n```swift\nlet oauth: OAuth = .init(.module)\n```\n\n### OAuth initialized with providers\nIf you are building your OAuth Providers programatically (recommended for production applications via a CI build pipeline for security purposes), you can pass providers and options as well.\n\n```swift\nlet providers: [OAuth.Provider] = ...\nlet options: [OAuth.Option: Any] = [\n    .applicationTag: \"com.bundle.identifier\",\n    .autoRefresh: true,\n    .useNonPersistentWebDataStore: true\n]\nlet oauth: OAuth = .init(providers: providers, options: options)\n```\n\n### OAuth initialized with custom URLSession\nTo support custom protocols or URL schemes that your app supports, developers can pass a custom **.urlSession** option that will allow the configuration of custom [URLProtocol](https://developer.apple.com/documentation/foundation/urlprotocol) classes that can handle the loading of protocol-specific URL data.\n\n```swift\n// Custom URLSession\nlet configuration: URLSessionConfiguration = .ephemeral\nconfiguration.protocolClasses = [CustomURLProtocol.self]\nlet urlSession: URLSession = .init(configuration: configuration)\n\nlet options: [OAuth.Option: Any] = [.urlSession: urlSession]\nlet oauth: OAuth = .init(.main, options: options)\n```\n\n### OAuth initialized with Keychain protection and Private Browsing\nOAuthKit allows you to protect access to your keychain items with biometrics until successful local authentication. If the **.requireAuthenticationWithBiometricsOrCompanion** option is set to true, the device owner will need to be authenticated by biometry or a companion device before keychain items (tokens) can be accessed. OAuthKit uses a default [LAContext](https://developer.apple.com/documentation/localauthentication/lacontext), but if you need fine-grained control while evaluating a user’s identity, pass your own custom [LAContext](https://developer.apple.com/documentation/localauthentication/lacontext) to the options.\n\nDevelopers can also implement private browsing by setting the **.useNonPersistentWebDataStore** option to true. This forces the [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview) used during authorization flows to use a non-persistent data store, preventing data from being written to the file system.\n\n\n```swift\n// Custom LAContext\nlet localAuthentication: LAContext = .init()\nlocalAuthentication.localizedReason = \"read tokens from keychain\"\nlocalAuthentication.localizedFallbackTitle = \"Use password\"\nlocalAuthentication.touchIDAuthenticationAllowableReuseDuration = 10\n\nlet options: [OAuth.Option: Any] = [\n    .localAuthentication: localAuthentication,\n    .requireAuthenticationWithBiometricsOrCompanion: true,\n    .useNonPersistentWebDataStore: true,\n]\nlet oauth: OAuth = .init(.main, options: options)\n```\n\n## OAuthKit Authorization Flows\nOAuth 2.0 authorization flows are started by calling the [oauth.authorize(provider:grantType:)](https://codefiesta.github.io/OAuthKit/documentation/oauthkit/oauth#Starting-an-authorization-flow) method.\n\nA good resource to help understand the detailed steps involved in OAuth 2.0 authorization flows can be found on the [OAuth 2.0 Playground](https://www.oauth.com/playground/index.html).\n\n```swift\noauth.authorize(provider: provider, grantType: grantType)\n``` \n\n\n### OAuth 2.0 Authorization Code Flow\nThe [Authorization Code](https://oauth.net/2/grant-types/authorization-code/) grant type is used by confidential and public clients to exchange an authorization code for an access token. It is recommended that all clients use the [PKCE](#oauth-20-pkce-flow) extension with this flow as well to provide better security.\n\n```swift\t\n// Generate a state and set the GrantType\nlet state: String = .secureRandom(32) // See String+Extensions\nlet grantType: OAuth.GrantType = .authorizationCode(state)\noauth.authorize(provider: provider, grantType: grantType)\n```\n\n### OAuth 2.0 PKCE Flow\nPKCE ([RFC 7636](https://www.rfc-editor.org/rfc/rfc7636)) is an extension to the [Authorization Code](https://oauth.net/2/grant-types/authorization-code/) flow to prevent CSRF and authorization code injection attacks.\n\nProof Key for Code Exchange ([PKCE](https://oauth.net/2/pkce/)) is the default and recommended flow to use in OAuthKit as this technique involves the client first creating a secret on each authorization request, and then using that secret again when exchanging the authorization code for an access token. This way if the code is intercepted, it will not be useful since the token request relies on the initial secret.\n\n```swift\n// PKCE is the default workflow with an auto generated pkce object\noauth.authorize(provider: provider)\n\n// Or you can specify the workflow to use PKCE and inject your own values\nlet grantType: OAuth.GrantType = .pkce(.init())\noauth.authorize(provider: provider, grantType: grantType)\n```\n\n### OAuth 2.0 Device Code Flow\nOAuthKit supports the [OAuth 2.0 Device Code Flow Grant](https://alexbilbie.github.io/2016/04/oauth-2-device-flow-grant/), which is used by apps that don't have access to a web browser (like tvOS or watchOS). To leverage OAuthKit in tvOS or watchOS apps, simply add the `deviceCodeURL` to your [OAuth.Provider](https://github.com/codefiesta/OAuthKit/blob/main/Sources/OAuthKit/OAuth+Provider.swift).\n\n```swift\nlet grantType: OAuth.GrantType = .deviceCode\noauth.authorize(provider: provider, grantType: grantType)\n```\n\n![tvOS-screenshot](https://github.com/user-attachments/assets/14997164-f86a-4ee0-b6b7-8c0d9732c83e)\n\n### OAuth 2.0 Client Credentials Flow\n\nThe OAuth 2.0 Client Credentials flow is a mechanism where a client application authenticates itself to an authorization server using its own credentials rather than a user's credentials. This flow is primarily used in server-to-server communication, where a service or application needs to access a protected resource without involving a user.\n\n```swift\nlet grantType: OAuth.GrantType = .clientCredentials\noauth.authorize(provider: provider, grantType: grantType)\n```\n\n## OAuth 2.0 Provider Debugging\nDebugging output with [debugPrint(\\_:separator:terminator:)](https://developer.apple.com/documentation/swift/debugprint(_:separator:terminator:)) into the standard output is disabled by default. If you need to inspect response data received from [providers](https://github.com/codefiesta/OAuthKit/blob/main/Sources/OAuthKit/OAuth+Provider.swift), you can toggle the `debug` value to true. You can see an [example here](https://github.com/codefiesta/OAuthKit/blob/main/Tests/OAuthKitTests/Resources/oauth.json).\n\n## OAuthKit Sample Application\nYou can find a sample application integrated with OAuthKit [here](https://github.com/codefiesta/OAuthSample).\n\n## Security Best Practices\n1. Use the [PKCE](https://github.com/codefiesta/OAuthKit?tab=readme-ov-file#oauth-20-pkce-flow) workflow if possible in your public applications.\n2. Never check in **clientID** or **clientSecret** values into source control. Although the **clientID** is public and the **clientSecret** is sensitive and private it is still widely regarded that *both* of these values should be always be treated as confidential.\n3. Don't include `oauth.json` files in your publicly distributed applications. It is possible for someone to [inspect and reverse engineer](https://www.nowsecure.com/blog/2021/09/08/basics-of-reverse-engineering-ios-mobile-apps/) the contents of your app and look at any files inside your app bundle which means you could potentially expose any confidential values contained in this file.\n4. Build OAuth Providers Programmatically via your CI Build Pipeline. Most continuous integration and delivery platforms have the ability to generate source code during build workflows that can get compiled into Swift byte code. It's should be feasible to write a step in the CI pipeline that generates a .swift file that provides access to a list of OAuth.Provider objects that have their confidential values set from the secure CI platform secret keys. This swift code can then compiled into the application as byte code. In practical terms, the security and obfuscation inherent in compiled languages make extracting confidential values difficult (but not impossible).\n5. OAuth 2.0 providers shouldn't provide the ability for publicly distributed applications to initiate [Client Credentials](https://github.com/codefiesta/OAuthKit?tab=readme-ov-file#oauth-20-client-credentials-flow) workflows since it is possible for someone to extract your secrets.\n\n\n## OAuth 2.0 Providers\nOAuthKit should work with any standard OAuth2 provider. Below is a list of tested providers along with their OAuth2 documentation links. If you’re interested in seeing support or examples for a provider not listed here, please open an issue on our [here](https://github.com/codefiesta/OAuthKit/issues).\n\n* [Auth0 / Okta](https://developer.okta.com/signup/)\n* [Box](https://developer.box.com/guides/authentication/oauth2/)\n* [Dropbox](https://developers.dropbox.com/oauth-guide)\n* [Github](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps)\n* [Google](https://developers.google.com/identity/protocols/oauth2)\n\t* **Important**: When creating a Google OAuth2 application from the [Google API Console](https://console.developers.google.com/) create an OAuth 2.0 Client type of Web Application (not iOS).\n* [LinkedIn](https://developer.linkedin.com/)\n\t* **Important**: When creating a LinkedIn OAuth2 provider, you will need to explicitly set the `encodeHttpBody` property to false otherwise the /token request will fail. Unfortunately, OAuth providers vary in the way they decode the parameters of that request (either encoded into the httpBody or as query parameters). See sample [oauth.json](https://github.com/codefiesta/OAuthKit/blob/main/Tests/OAuthKitTests/Resources/oauth.json).\n\t* LinkedIn currently doesn't support **PKCE**.\n* [Instagram](https://developers.facebook.com/docs/instagram-basic-display-api/guides/getting-access-tokens-and-permissions)\n* [Microsoft](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow)\n    * **Important**: When registering an application inside the [Microsoft Azure Portal](https://portal.azure.com/) it's important to choose a **Redirect URI** as **Web** otherwise the `/token` endpoint will return an error when sending the `client_secret` in the body payload.\n* [Slack](https://api.slack.com/authentication/oauth-v2)\n    * **Important**: Slack will block unknown browsers from initiating OAuth workflows. See sample [oauth.json](https://github.com/codefiesta/OAuthKit/blob/main/Tests/OAuthKitTests/Resources/oauth.json) for setting the `customUserAgent` as a workaround.\n* [Stripe](https://docs.stripe.com/stripe-apps/api-authentication/oauth)\n* [Twitter](https://developer.x.com/en/docs/authentication/oauth-2-0)\n\t* **Unsupported**: Although OAuthKit *should* work with Twitter/X OAuth2 APIs without any modification, **@codefiesta** has chosen not to support any [Elon Musk](https://www.natesilver.net/p/elon-musk-polls-popularity-nate-silver-bulletin) backed ventures due to his facist, racist, and divisive behavior that epitomizes out-of-touch wealth and greed. **@codefiesta** will not raise objections to other developers who wish to contribute to OAuthKit in order to support Twitter OAuth2.\n\n## OAuthKit Documentation\n\nYou can find the complete Swift DocC documentation for the [OAuthKit Framework here](https://codefiesta.github.io/OAuthKit/documentation/oauthkit/).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodefiesta%2Foauthkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodefiesta%2Foauthkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodefiesta%2Foauthkit/lists"}