You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS Swift中LinkedIn OAuth 2.0认证实现及重定向URL配置问题

Hey there, let's work through this LinkedIn OAuth 2.0 migration issue step by step—your redirect URI mismatch is the core problem here, and we'll fix that first, then walk through a full working implementation.


First: Fix the "redirect_uri does not match" Error

This error happens because your app's configured redirect URL doesn't exactly match what's registered in LinkedIn's developer portal. Since you want to redirect back to your app, you'll need to set up a custom URL scheme:

1. Configure Custom URL Scheme in Xcode

  • Open your project's Info.plist
  • Right-click → Add Row → Select URL types
  • Expand the item:
    • Set URL Schemes to a unique value (e.g., myapplinkedin—avoid generic names like myapp to reduce conflicts)
    • You can leave Role as Editor and URL Identifier as your app's bundle ID for clarity

2. Register the Redirect URI in LinkedIn Developer Portal

  • Log into the LinkedIn Developer Portal and open your app
  • Navigate to Auth → OAuth 2.0 settings
  • In the Authorized redirect URLs for your app section, add your custom URL (e.g., myapplinkedin://auth—the path auth is optional but helps distinguish auth redirects from other app links)
  • Save your changes (this can take a few minutes to propagate, so don't panic if it doesn't work immediately)

⚠️ Critical: The redirect URI in your code must exactly match what you registered here—no typos, extra slashes, or case differences.


Full OAuth 2.0 Implementation for iOS (No Deprecated SDKs)

We'll use the authorization code flow (LinkedIn's recommended secure flow for mobile apps) instead of the old SDK. Here's the step-by-step code:

Step 1: Build the Authorization Request URL

We'll construct the URL that opens LinkedIn's login page via SFSafariViewController (LinkedIn discourages using WebViews for security):

import SafariServices

func buildLinkedInAuthURL() -> URL? {
    let baseURL = "https://www.linkedin.com/oauth/v2/authorization"
    let components = URLComponents(string: baseURL)?
        .addingQueryItem(name: "response_type", value: "code") // Authorization code flow
        .addingQueryItem(name: "client_id", value: "YOUR_LINKEDIN_CLIENT_ID")
        .addingQueryItem(name: "redirect_uri", value: "myapplinkedin://auth") // Match your registered URI
        .addingQueryItem(name: "scope", value: "r_liteprofile r_emailaddress") // Request required scopes
        .addingQueryItem(name: "state", value: UUID().uuidString) // CSRF protection
    return components?.url
}

Step 2: Launch the Login Flow

func startLinkedInAuth() {
    guard let authURL = buildLinkedInAuthURL() else {
        print("Failed to build auth URL")
        return
    }
    let safariVC = SFSafariViewController(url: authURL)
    safariVC.delegate = self
    present(safariVC, animated: true)
}

Step 3: Handle the Redirect Back to Your App

Add this code to your SceneDelegate (iOS 13+) or AppDelegate:

For SceneDelegate:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    handleLinkedInRedirect(url: url)
}

private func handleLinkedInRedirect(url: URL) {
    guard url.scheme == "myapplinkedin", url.host == "auth" else { return }
    
    let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
    guard let code = components?.queryItems?.first(where: { $0.name == "code" })?.value else {
        print("No authorization code found in redirect")
        return
    }
    
    // Exchange code for access token
    exchangeCodeForAccessToken(code: code)
}

For AppDelegate:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    handleLinkedInRedirect(url: url)
    return true
}

private func handleLinkedInRedirect(url: URL) {
    // Same code as above for parsing the authorization code
}

Step 4: Exchange Authorization Code for Access Token

Send a POST request to LinkedIn's token endpoint to get your access token:

func exchangeCodeForAccessToken(code: String) {
    let tokenURL = "https://www.linkedin.com/oauth/v2/accessToken"
    let parameters = [
        "grant_type": "authorization_code",
        "code": code,
        "client_id": "YOUR_LINKEDIN_CLIENT_ID",
        "client_secret": "YOUR_LINKEDIN_CLIENT_SECRET",
        "redirect_uri": "myapplinkedin://auth" // Match your registered URI
    ]
    
    var request = URLRequest(url: URL(string: tokenURL)!)
    request.httpMethod = "POST"
    request.httpBody = parameters.percentEncoded()
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    
    URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, error == nil else {
            print("Token exchange error: \(error?.localizedDescription ?? "Unknown error")")
            return
        }
        
        do {
            if let tokenResponse = try JSONSerialization.jsonObject(with: data) as? [String: Any],
               let accessToken = tokenResponse["access_token"] as? String {
                // Success! Use the token to fetch user data
                DispatchQueue.main.async {
                    let alertVC = UIAlertController(title: "Success", message: "Access Token: \(accessToken)", preferredStyle: .alert)
                    alertVC.addAction(UIAlertAction(title: "OK", style: .default))
                    self.present(alertVC, animated: true)
                }
            }
        } catch {
            print("Failed to parse token response: \(error.localizedDescription)")
        }
    }.resume()
}

// Helper extension to encode POST parameters
extension Dictionary where Key == String, Value == String {
    func percentEncoded() -> Data? {
        return map { key, value in
            let escapedKey = key.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
            let escapedValue = value.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
            return "\(escapedKey)=\(escapedValue)"
        }
        .joined(separator: "&")
        .data(using: .utf8)
    }
}

extension CharacterSet {
    static let urlQueryValueAllowed: CharacterSet = {
        let generalDelimitersToEncode = ":#[]@"
        let subDelimitersToEncode = "!$&'()*+,;="
        var allowed = CharacterSet.urlQueryAllowed
        allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
        return allowed
    }()
}

Key Notes to Avoid Issues

  • Scopes: Make sure the scopes you request (e.g., r_liteprofile) are approved in your LinkedIn app's Auth → Scopes section. Unapproved scopes will cause errors.
  • Security: Store your access token in Keychain instead of UserDefaults for better security.
  • Third-Party Libraries: If you prefer using a library, look for ones actively maintained for LinkedIn's current OAuth 2.0 flow (avoid outdated libraries that rely on the deprecated SDK).

内容的提问来源于stack exchange,提问作者swapnil patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:46:45