{"id":21803888,"url":"https://github.com/hughrawlinson/spotify-implicit-grant-macos","last_synced_at":"2025-03-21T07:24:51.282Z","repository":{"id":143365873,"uuid":"91945246","full_name":"hughrawlinson/spotify-implicit-grant-macos","owner":"hughrawlinson","description":"An example Spotify Implicit Grant OAuth flow implementation in macOS/Swift","archived":false,"fork":false,"pushed_at":"2017-05-21T10:09:58.000Z","size":63,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-26T04:12:50.459Z","etag":null,"topics":["guide","macos","spotify","spotify-authentication"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hughrawlinson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-21T09:11:37.000Z","updated_at":"2022-10-28T12:12:05.000Z","dependencies_parsed_at":null,"dependency_job_id":"cbe064dc-28d3-4cc0-a87c-3aac4a8755bc","html_url":"https://github.com/hughrawlinson/spotify-implicit-grant-macos","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hughrawlinson%2Fspotify-implicit-grant-macos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hughrawlinson%2Fspotify-implicit-grant-macos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hughrawlinson%2Fspotify-implicit-grant-macos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hughrawlinson%2Fspotify-implicit-grant-macos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hughrawlinson","download_url":"https://codeload.github.com/hughrawlinson/spotify-implicit-grant-macos/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244754180,"owners_count":20504661,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["guide","macos","spotify","spotify-authentication"],"created_at":"2024-11-27T11:51:47.440Z","updated_at":"2025-03-21T07:24:51.263Z","avatar_url":"https://github.com/hughrawlinson.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"I decided to spend some time this weekend implementing the Spotify OAuth Implicit\nGrant flow in a macOS app. I haven't spent a lot of time working with macOS\ndevelopment in the past, so it was quite the voyage of discovery for me.\n\nThe Implicit Grant OAuth flow is the simplest flow to implement to facilitate\n**user authorization** for your app. It doesn't return a refresh token, but\nbecause the flow doesn't require the client secret, there's no serverside\ncomponent to this auth implementation.\n\n## 1. Create a new project\nOpen up XCode, and open the 'New Project' wizard. Select 'Cocoa Application' as\nyour application type, and hit 'next'. Enter a product name, set your language\nto 'Swift', and you'll be good to go!\n\n## 2. Register a new Spotify Application\n\nFollow Spotify's [Register Your Application][1]\nguide to create a new set of Spotify app credentials.\n\n## 3. Prepare some variables\n\nIn your `AppDelegate.swift`, you'll need to set up some variables to use when\nconstructing your authorize link. You can put these inside your AppDelegate\nclass.\n\n```swift\nlet spotifyAccountsBaseUri = \"https://accounts.spotify.com\"\nlet spotifyAccountsAuthorizeUri = \"\\(spotifyAccountsBaseUri)/authorize\"\nlet clientId = \"[YOUR-CLIENT-ID]\"\nlet uriSchemeBase = \"my-awesome-app\"\nlet redirectUri = \"\\(uriSchemeBase)://spotifyOauthCallback\"\n```\n\nLets unpack what's going on here. We're setting up a `spotifyAccountsBaseUri`,\nwhich is the URI for the Spotify accounts service. We use it immediately in the\ntemplate string for `spotifyAccountsAuthorizeUri`, the base URI that you present\nto your users to let them authorize your app to work with their Spotify account.\n\nNext, we set up a client ID. This is available in the Spotify application you\nset up in [Your Applications][2]. We'll use this\nto present the authorization dialog for your app to your users.\n\nWe're going to use a URI Scheme to handle the redirection from the Spotify\naccounts service back to your application once the user has authorized (or\ndecided not to authorize) your app. To handle this, we create a `uriSchemeBase`.\nYou can choose your own URI Scheme base, but it should be unique to your\napplication, and should be in [kebab-case][3], with all\nletters lowercase. We'll modify your `App.plist` to tell macOS that your\napplication can handle your URI schema in a later step.\n\nFinally, we register a `redirectUri`. This is the specific URI that your users\nwill be directed to once they've completed their steps of the implicit grant\nflow. We'll listen for requests made to this URI later on in order to recieve\nour access token. At this stage, you should take your full redirect uri (in this\ncase, `my-awesome-amm://spotifyOauthCallback`), and whitelist it in your\napplication settings page on [developer.spotify.com][4].\nRemember to click save!\n\n## 4. Present Spotify authorization dialog to user\n\nWhen our application launches, we'll want to present the Spotify authorization\ndialog to our user so that they can authorize our application, and we can start\ncalling the Spotify API. In the previous step, we created our\n`spotifyAccountsAuthorizeUri`, which we'll now configure to authorize using the\nclient credentials flow, with your app's client id, and to redirect to the\ncorrect place.\n\nThe accounts service takes our `redirect_uri` as a parameter - it has to be URL\nencoded. In your AppDelegate template, you will see the stub function\n`applicationDidFinishLaunching`. This method is called once the application has\nfinished launching - an excellent time to ask the user to authorize the app!\nInside that function, insert the following code:\n\n```swift\nlet characterSet = NSMutableCharacterSet.alphanumeric()\ncharacterSet.addCharacters(in: \"-_.!~*'()\")\nlet urlEncodedRedirectUri =   redirectUri.addingPercentEncoding(withAllowedCharacters: characterSet as CharacterSet)!\n```\n\nThis takes the redirectUri we set up earlier and ensures that it's properly\nencoded to work as a query parameter for our authorization URI.\n\nNow we can construct the URI:\n\n```swift\nlet authorizationUri = \"\\(spotifyAccountsAuthorizeUri)?response_type=token\u0026client_id=\\(clientId)\u0026redirect_uri=\\(urlEncodedRedirectUri)\"\n```\n\nThis line assigns a new constant `authorizationUri`, inserting a few query\nparameters. The `response_type` parameter tells the Spotify accounts service\nthat we're following the _Implicit Grant_ flow as opposed to any of the other\nsupported authorization flows. We also provide the client ID of our application,\nand the redirect_uri that we want the user to be redirected back to once they\ncomplete the authorization dialog. If you were to print out the\n`authorizationUri`, it would look something like this.\n\n```\nhttps://accounts.spotify.com/authorize?response_type=token\u0026client_id=[YOUR-CLIENT-ID]\u0026redirect_uri=my-awesome-app%3A%2F%2FspotifyOauthCallback\n```\n\nYou can add scopes as a comma separated list of scope names (i.e.\n`user-read-recently-played,user-modify-playback-state`) as a value of the\n'scopes' query parameter, but for this example we don't need any extra special\nscopes.\n\nTo present it to the user, you can evaluate the following expression:\n\n```swift\nif let url = URL(string: authorizationUri), NSWorkspace.shared().open(url) {\n    print(\"Opened Spotify authorization dialog in user's default browser\")\n}\n```\n\n## 5. Recieving the request on your URI schema\n\nAt this point, if you run your app you should see your browser open to the\nauthorization uri, presenting an oauth dialog to your user (or asking them to\nlog in). If you get an error like \"Invalid Client\" or \"Invalid Redirect URI\",\nmake sure you've set your client id and redirect uris correctly at the top of\nyour file, and that you've definitely added **and saved** your redirect URI in\nyour Spotify Application Settings page.\n\nBut when you click 'Okay' on the Spotify Authorization Dialog, nothing happens!\nThis is because our app isn't yet listening to requests on your URI schema, so\nit can't pick up the access token that the accounts service has tried to pass to\nit.\n\nTo fix this, we'll code up an event handler that listens to requests on our URI\nschema. We'll add two functions to our `AppDelegate` class.\n\n```swift\nfunc applicationWillFinishLaunching(_ notification: Notification) {\n    let appleEventManager: NSAppleEventManager = NSAppleEventManager.shared()\n    appleEventManager.setEventHandler(self, andSelector: #selector(handleGetURLEvent(event:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))\n}\n```\n\n`applicationWillFinishLaunching` will be called just before the application\nfinishes launching - this is when we need to register our listener. We tell the\nApple Event Manager to call our `handleGetURLEvent` function whenever it\nrecieves a request with a specific EventClass and EventID.\n\nNow we'll implement our handler that `appleEventManager` will call.\n\n```swift\n// Set up a field on the AppDelegate class to store our accessToken\nvar accessToken: String? = nil\n\nfunc handleGetURLEvent(event: NSAppleEventDescriptor) {\n    guard let fullUrl = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else {\n        return\n    }\n\n    guard let fragmentComponentsURL = URL(string: fullUrl) else {\n        return\n    }\n\n    guard let fragmentComponentQueryItems = NSURLComponents(string: \"?\\((fragmentComponentsURL.fragment)!)\")?.queryItems else {\n        return\n    }\n\n    fragmentComponentQueryItems.forEach({ (item) in\n        if item.name == \"access_token\" {\n            accessToken = item.value\n            print(accessToken)\n        }\n    })\n}\n```\n\nWe also need to tell macOS that our application can handle requests via our URI\nSchema. To do this, we add a property to our app's `Info.plist`. You'll need to\nadd a property `URL Types`, which should give you an array with 1 item\n(`Item 0`) containing a `URL Identifier`. To `Item 0`, you add a `URI Schemes`\nproperty, which will give you another array with 1 item called `Item 0`. Set the\nvalue of the inner `Item 0` to the value of your `uriSchemeBase` constant, set\nat the top of your `AppDelegate`. In this example, it's `my-awesome-app`. Now\nyour app should be ready to handle requests using your URI Scheme!\n\n![URL Scheme in Info.plist][5]\n\n## 6. Use access token to query the Spotify API\n\nNow that we've successfully completed the Implicit Grant OAuth flow, it's time\nto use the access token we got to make a request to the Spotify API. Add this\nmethod to your App Delegate. It makes a call to the API, converts the response\nJSON String to a Swift object, and passes the value to a closure it recieves as\nan argument.\n\n```swift\nfunc getSpotifyUserDetails(dataHandler: @escaping ([String: Any]) -\u003e Void) {\n    if (accessToken != nil) {\n        var request = URLRequest(url: URL(string: \"https://api.spotify.com/v1/me\")!)\n        request.httpMethod = \"GET\"\n        request.addValue(\"Bearer \\(accessToken!)\", forHTTPHeaderField: \"Authorization\")\n        let task = URLSession.shared.dataTask(with: request) { data, response, error in\n            guard let data = data, error == nil else {\n                print(\"error=\\(String(describing: error))\")\n                return\n            }\n\n            if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {\n                print(\"statusCode should be 200, but is \\(httpStatus.statusCode)\")\n                print(\"response = \\(String(describing: response))\")\n            }\n            if let json = try? JSONSerialization.jsonObject(with: data, options: []) {\n                if let dictionary = json as? [String: Any] {\n                    dataHandler(dictionary)\n                }\n            }\n        }\n        task.resume()\n    }\n}\n```\n\nFinally, we'll execute this function inside our redirect URI handler. The full\n`handleGetURLEvent` should look like this:\n\n```swift\nfunc handleGetURLEvent(event: NSAppleEventDescriptor) {\n    guard let fullUrl = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else {\n        return\n    }\n\n    guard let fragmentComponentsURL = URL(string: fullUrl) else {\n        return\n    }\n\n    guard let fragmentComponentQueryItems = NSURLComponents(string: \"?\\((fragmentComponentsURL.fragment)!)\")?.queryItems else {\n        return\n    }\n\n    fragmentComponentQueryItems.forEach({ (item) in\n        if item.name == \"access_token\" {\n            accessToken = item.value\n            getSpotifyUserDetails(dataHandler: { (details) in\n                if details[\"display_name\"] != nil {\n                    print(\"Congrats on implementing the Spotify Implicit Grant flow in your macOS Application, \\(details[\"display_name\"]!)!\")\n                }\n            })\n        }\n    })\n}\n```\n\nNow when you run the app and sign in, you should see this printed to your\nconsole: `Congrats on implementing the Spotify Implicit Grant flow in your macOS\nApplication, Hugh Rawlinson!`, but with your Spotify Display Name, rather than\nmine.\n\n# Summary\n\nCongrats on implementing the Implicit Grant OAuth flow in your macOS app! You've\ndone well! The entire `AppDelegate.swift` is available in this repo. If you need\nany more help writing Spotify applications for macOS or any other platform,\nplease reach out to [@SpotifyPlatform][6] on\nTwitter. Happy hacking!\n\n [1]: https://developer.spotify.com/web-api/tutorial/#registering-your-application\n [2]: https://developer.spotify.com/my-applications/#!/applications\n [3]: https://en.wikipedia.org/wiki/Letter_case#Special_case_styles\n [4]: //developer.spotify.com\n [5]: https://raw.githubusercontent.com/hughrawlinson/spotify-implicit-grant-macos/master/URL_Scheme.png\n [6]: https://twitter.com/spotifyplatform\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhughrawlinson%2Fspotify-implicit-grant-macos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhughrawlinson%2Fspotify-implicit-grant-macos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhughrawlinson%2Fspotify-implicit-grant-macos/lists"}