Skip to content

spotify/ios-auth

Repository files navigation

SpotifyLogin

Overview

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.

Minimum Requirements

  • iOS 13.0 or later

Features

  • ✅ 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

Table of Contents

Getting Started

Prepare Your Environment

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.

Installation

The SDK can be integrated into your project using one of the following methods:

Manually (XCFramework)

  1. Download the SpotifyLogin.xcframework from the latest release
  2. Add SpotifyLogin.xcframework to your project by dragging and dropping it into Frameworks, Libraries, and Embedded Content in your Xcode project settings
  3. Make sure the framework is set to Embed & Sign

Swift Package Manager

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")
]

CocoaPods

To integrate using CocoaPods, add the following to your Podfile:

pod 'Spotify-iOS-Auth', '~> 3.0.1'

Setup

After installation, configure the following:

  • Add the Spotify scheme to your app's Info.plist file:
<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:

    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)

Usage

Configuration

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")!
)

SessionManager

Initialize a SessionManager with your configuration and assign a delegate that conforms to SessionManagerDelegate:

let sessionManager = SessionManager(configuration: configuration, delegate: self)

Implement SessionManagerDelegate

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)")
    }
}

Handle Redirect Callbacks

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)
}

Start Authorization

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.

Authorization Flows

The SDK supports four different authorization flows to accommodate various use cases. Specify the flow when calling initiateSession:

1. .default (Recommended)

The most user-friendly flow with automatic fallback:

  1. If Spotify app is installed → Opens Spotify app for authentication
  2. 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)

2. .clientOnlyRedirectAppStore

Encourages users to install the Spotify app:

  1. If Spotify app is installed → Opens Spotify app for authentication
  2. 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)

3. .clientOnlyFailsIfNotInstalled

Strict app-only authentication:

  1. If Spotify app is installed → Opens Spotify app for authentication
  2. 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)

4. .spotifySchemeNotRegistered

Special flow for apps that cannot declare the Spotify URL scheme:

  1. Attempts to open Spotify app directly (without checking canOpenURL)
  2. 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)

PKCE vs Token Swap

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 exchange

Token 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 PKCE

Your 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 up

The server will start on http://localhost:1234.

For more details, see the TokenSwap/README.

Manual Code Exchange

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
    }
}

Checking Spotify App Installation

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.

Token Refresh

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.

Campaign Attribution

Track where users are coming from by adding a campaign parameter:

sessionManager.initiateSession(
    with: scopes,
    authorizationFlow: .default,
    campaign: "summer_promotion"
)

Storing Sessions Securely

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
}

Error Handling

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")
        }
    }
}

Demo Project

The SDK includes a complete demo project showing best practices. See the DemoProject folder for a working example using SwiftUI.

Support

For issues and questions:

License

This SDK is licensed under the Apache License 2.0. See the LICENSE file for details.

About

Spotify authentication and authorization for iOS.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •