SpotifyLogin is a lightweight framework that enables your iOS application to authenticate users with Spotify and obtain access tokens for calling the Spotify Web API. The SDK handles the complete OAuth 2.0 authorization flow, including PKCE (Proof Key for Code Exchange) for secure authentication.
- iOS 13.0 or later
- ✅ Seamless authentication via Spotify iOS app or web browser
- ✅ Automatic PKCE implementation for secure OAuth 2.0 flow
- ✅ Multiple authorization flows to fit different use cases
- ✅ Automatic token exchange and session management
- ✅ Optional manual code exchange for custom implementations
- ✅ Support for both Universal Links (recommended) and custom URL schemes
- ✅ Token refresh capability for long-lived sessions
- Getting Started
- Usage
- Authorization Flows
- PKCE vs Token Swap
- Manual Code Exchange
- Checking Spotify App Installation
- Token Refresh
- Campaign Attribution
- Storing Sessions Securely
- Error Handling
- Demo Project
- Support
- License
Register your application at the Spotify Developer Dashboard to obtain a Client ID. During registration, you must configure a Redirect URI that Spotify will use to call back to your app after authorization.
Important: Make sure to add your bundle ID and enable the iOS SDK option in your app settings on the Developer Dashboard.
The SDK can be integrated into your project using one of the following methods:
- Download the
SpotifyLogin.xcframeworkfrom the latest release - Add
SpotifyLogin.xcframeworkto your project by dragging and dropping it into Frameworks, Libraries, and Embedded Content in your Xcode project settings - Make sure the framework is set to Embed & Sign
Add the SDK as a dependency in your Package.swift or directly in Xcode:
dependencies: [
.package(url: "https://github.com/spotify/ios-auth.git", from: "3.0.1")
]To integrate using CocoaPods, add the following to your Podfile:
pod 'Spotify-iOS-Auth', '~> 3.0.1'After installation, configure the following:
- Add the Spotify scheme to your app's
Info.plistfile:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>spotify</string>
</array>-
Configure your Redirect URI:
Option A: Universal Links (Recommended)
- Use an
https://redirect URI (e.g.,https://yourdomain.com/spotify-callback) - Set up Associated Domains in Signing & Capabilities:
- Add
applinks:yourdomain.com - Add
webcredentials:yourdomain.com - Configure your domain's .well-known/apple-app-site-association file
- Add
Option B: Custom URL Scheme
- Use a custom scheme (e.g.,
myapp://callback) - Add your redirect URI under URL Types in your project settings:
- URL Schemes:
myapp - Identifier: A unique identifier (e.g.,
com.yourcompany.myapp)
- URL Schemes:
- Use an
Initialize a Configuration object with your Client ID and Redirect URI:
import SpotifyLogin
let configuration = Configuration(
clientID: "your_client_id",
redirectURL: URL(string: "your_redirect_uri")!
)Initialize a SessionManager with your configuration and assign a delegate that conforms to SessionManagerDelegate:
let sessionManager = SessionManager(configuration: configuration, delegate: self)Handle authentication success and failure:
extension YourClass: SessionManagerDelegate {
func sessionManager(manager: SessionManager, didInitiate session: Session) {
// Authentication successful - use session.accessToken for API calls
print("Access Token: \(session.accessToken)")
print("Expires: \(session.expirationDate)")
}
func sessionManager(manager: SessionManager, didFailWith error: Error) {
// Authentication failed
print("Auth failed: \(error.localizedDescription)")
}
}Configure your app to handle the redirect callback from Spotify.
For UIKit with SceneDelegate:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
// Use this method if your redirect URI is a custom URL scheme
guard let url = URLContexts.first?.url else { return }
sessionManager.openURL(url)
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
// Use this method if your redirect URI is a universal link (https)
sessionManager.continueUserActivity(userActivity)
}For SwiftUI:
.onOpenURL { url in
sessionManager.openURL(url)
}Initiate the authorization flow with the scopes your app needs:
let scopes: [Scope] = [.playlistModifyPublic, .playlistReadPrivate]
sessionManager.initiateSession(with: scopes)See the full list of available scopes in Scope.swift or the Spotify Web API documentation.
The SDK supports four different authorization flows to accommodate various use cases. Specify the flow when calling initiateSession:
The most user-friendly flow with automatic fallback:
- If Spotify app is installed → Opens Spotify app for authentication
- If Spotify app is not installed → Opens web browser (ASWebAuthenticationSession)
Use this when: You want the best user experience with automatic fallback.
sessionManager.initiateSession(with: scopes, authorizationFlow: .default)Encourages users to install the Spotify app:
- If Spotify app is installed → Opens Spotify app for authentication
- If Spotify app is not installed → Redirects to App Store
Use this when: You require authentication through the Spotify app only.
sessionManager.initiateSession(with: scopes, authorizationFlow: .clientOnlyRedirectAppStore)Strict app-only authentication:
- If Spotify app is installed → Opens Spotify app for authentication
- If Spotify app is not installed → Fails with error
Use this when: You require authentication through the Spotify app only.
sessionManager.initiateSession(with: scopes, authorizationFlow: .clientOnlyFailsIfNotInstalled)Special flow for apps that cannot declare the Spotify URL scheme:
- Attempts to open Spotify app directly (without checking
canOpenURL) - If opening fails → Falls back to web browser
Use this when: You've reached the limit of LSApplicationQueriesSchemes in your Info.plist and cannot add the spotify scheme.
sessionManager.initiateSession(with: scopes, authorizationFlow: .spotifySchemeNotRegistered)The SDK supports two methods for exchanging authorization codes for access tokens:
PKCE (Proof Key for Code Exchange) is a secure OAuth 2.0 extension designed for mobile and native applications.
It's the default behavior, no additional configuration needed:
let configuration = Configuration(
clientID: "your_client_id",
redirectURL: URL(string: "your_redirect_uri")!
)
let sessionManager = SessionManager(configuration: configuration, delegate: self)
sessionManager.initiateSession(with: scopes)
// SDK automatically handles PKCE and token exchangeToken Swap is an approach where your backend server exchanges the authorization code for tokens.
Configure token swap and refresh URLs:
let configuration = Configuration(
clientID: "your_client_id",
redirectURL: URL(string: "your_redirect_uri")!
)
configuration.tokenSwapURL = URL(string: "https://yourbackend.com/api/token")
configuration.tokenRefreshURL = URL(string: "https://yourbackend.com/api/refresh_token")
let sessionManager = SessionManager(configuration: configuration, delegate: self)
sessionManager.initiateSession(with: scopes)
// SDK will use token swap instead of PKCEYour backend server needs two endpoints:
/swap- Exchanges authorization code for access token/refresh- Refreshes an expired access token
For testing, we provide a complete Ruby/Sinatra token swap server that you can run with Docker:
cd TokenSwap
docker-compose upThe server will start on http://localhost:1234.
For more details, see the TokenSwap/README.
The SDK provides automatic token exchange by default, but you can handle it manually if needed for custom requirements.
Implement the optional shouldRequestAccessTokenWith delegate method:
extension YourClass: SessionManagerDelegate {
// Return false to handle token exchange manually
func sessionManager(manager: SessionManager, shouldRequestAccessTokenWith code: String) -> Bool {
// Perform manual exchange
exchangeCodeForToken(code)
return false // Tell SDK not to do automatic exchange
}
}Check if the Spotify app is installed before initiating authorization:
if sessionManager.isSpotifyAppInstalled {
print("Spotify is installed")
} else {
print("Spotify is not installed")
}Note: This requires the spotify scheme in LSApplicationQueriesSchemes.
Access tokens are short-lived and expire after a period of time (typically 1 hour). When an access token expires, you'll need to refresh it to continue making API calls.
Check if your current session's access token has expired and manually trigger a token refresh:
if session.isExpired {
sessionManager.renewSession()
}Implement the didRenew delegate method to receive the new access token:
extension YourClass: SessionManagerDelegate {
func sessionManager(manager: SessionManager, didRenew session: Session) {
// Handle successful token refresh
print("Token renewed successfully")
print("New Access Token: \(session.accessToken)")
print("Expires at: \(session.expirationDate)")
// Update your stored session if you're persisting it
}
}Token renewal can fail for various reasons (network issues, expired refresh token, revoked access). Handle failures appropriately:
func sessionManager(manager: SessionManager, didFailWith error: Error) {
if let spotifyError = error as? SpotifyError {
switch spotifyError {
case .renewSessionFailed:
// Prompt user to log in again
sessionManager.initiateSession(with: requiredScopes)
...
}
}
}Note: If token refresh fails (e.g., refresh token has expired), you'll need to initiate a new authorization flow to obtain a fresh access token.
Track where users are coming from by adding a campaign parameter:
sessionManager.initiateSession(
with: scopes,
authorizationFlow: .default,
campaign: "summer_promotion"
)The Session object conforms to NSSecureCoding for secure storage:
// Save session
if let session = sessionManager.session {
let data = try? NSKeyedArchiver.archivedData(
withRootObject: session,
requiringSecureCoding: true
)
// Store in Keychain or secure storage
}
// Restore session
if let data = loadFromKeychain() {
let session = try? NSKeyedUnarchiver.unarchivedObject(
ofClass: Session.self,
from: data
)
sessionManager.session = session
}The SDK provides detailed error information through SpotifyError:
func sessionManager(manager: SessionManager, didFailWith error: Error) {
if let spotifyError = error as? SpotifyError {
switch spotifyError {
case .authorizationCancelled:
print("User cancelled authorization")
case .accessDenied:
print("User denied access")
case .authorizationFailed:
print("Authorization failed")
case .renewSessionFailed:
print("Token refresh failed")
}
}
}The SDK includes a complete demo project showing best practices. See the DemoProject folder for a working example using SwiftUI.
For issues and questions:
- Check the Spotify Developer Documentation
- Review the demo project included with the SDK
- Open an issue on the GitHub repository
This SDK is licensed under the Apache License 2.0. See the LICENSE file for details.