使用MFA构建安全的iOS应用

本文详细介绍了如何使用Okta身份平台和Swift SDK构建支持多因素认证的安全iOS应用,包括OAuth 2.0配置、认证服务实现、令牌管理和后端API集成等完整技术方案。

如何使用MFA构建安全的iOS应用

现代移动应用需要强大的安全解决方案,特别是在处理敏感用户数据或企业级访问时。Okta提供了一个强大的身份平台,通过其Swift SDK中的BrowserSignIn模块,为iOS应用添加安全登录变得可扩展且简单。

在本文中,您将学习如何:

  • 设置Okta开发者账户
  • 使用最佳实践为iOS应用配置认证
  • 使用MFA策略自定义认证体验
  • 创建可测试的AuthService协议
  • 展示如何在SwiftUI中集成AuthService的示例

使用Okta实现OAuth 2.0和OpenID Connect

第一步是在Okta中将您的应用注册为OpenID Connect客户端,使用带有PKCE的授权码流程,这是最安全且移动友好的OAuth 2.0流程。

配置步骤

  1. 在管理控制台中创建应用集成
  2. 选择OIDC - OpenID Connect作为登录方法
  3. 选择Native Application作为应用类型
  4. 配置重定向URI:com.okta.{yourOktaDomain}:/callback
  5. 设置适当的访问级别

优先使用防钓鱼认证因素

每个新的Integrator Free Plan管理员账户默认必须使用Okta Verify应用来设置MFA。我们推荐使用防钓鱼因素,如带有生物识别功能的Okta Verify和带有WebAuthn的FIDO2。

创建iOS项目并集成Okta移动库

前提条件

  • Xcode 15.0或更高版本
  • Swift 5+功能
  • Swift Package Manager
  • 本地安装的Node和npm

添加Okta SDK

使用Swift Package Manager将Okta SDK添加到项目中:

1
2
// 在Package Dependencies中添加
https://github.com/okta/okta-mobile-swift

配置OIDC

创建Okta.plist文件来管理配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<plist version="1.0">
<dict>
    <key>scopes</key>
    <string>openid profile offline_access</string>
    <key>redirectUri</key>
    <string>com.okta.{yourOktaDomain}:/callback</string>
    <key>clientId</key>
    <string>{yourClientID}</string>

    <string>{yourOktaDomain}/oauth2/default</string>
    <key>logoutRedirectUri</key>
    <string>com.okta.{yourOktaDomain}:/</string>
</dict>
</plist>

实现认证服务

AuthService协议

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
protocol AuthServiceProtocol {
    var isAuthenticated: Bool { get }
    var idToken: String? { get }
    
    func tokenInfo() -> TokenInfo?
    func userInfo() async throws -> UserInfo?
    func signIn() async throws
    func signOut() async throws
    func refreshTokenIfNeeded() async throws
}

登录实现

1
2
3
4
5
6
7
8
@MainActor
func signIn() async throws {
    BrowserSignin.shared?.ephemeralSession = true
    let tokens = try await BrowserSignin.shared?.signIn()
    if let tokens {
      _ = try? Credential.store(tokens)
    }
}

令牌管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var isAuthenticated: Bool {
  return Credential.default != nil
}

var idToken: String? {
  return Credential.default?.token.idToken?.rawValue
}

func refreshTokenIfNeeded() async throws {
  guard let credential = Credential.default else { return }
  try await credential.refresh()
}

在SwiftUI中使用认证服务

AuthViewModel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Observable
final class AuthViewModel {
    private let authService: AuthServiceProtocol
    
    var isAuthenticated: Bool = false
    var idToken: String?
    var isLoading: Bool = false
    
    @MainActor
    func handleAuthAction() async {
        setLoading(true)
        defer { setLoading(false) }

        do {
            if isAuthenticated {
                try await authService.signOut()
            } else {
                try await authService.signIn()
            }
            updateUI()
        } catch {
            errorMessage = error.localizedDescription
        }
    }
}

添加后端授权

设置资源服务器

使用Node.js示例资源服务器:

1
2
3
4
const oktaJwtVerifier = new OktaJwtVerifier({

  clientId: '{yourClientID}',
});

从iOS应用发起授权API请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@MainActor
func fetchMessageFromBackend() async throws -> String {
  guard let credential = Credential.default else {
      return "Not authenticated."
  }

  var request = URLRequest(url: URL(string: "http://localhost:8000/api/messages")!)
  request.httpMethod = "GET"

  await credential.authorize(&request)

  let (data, _) = try await URLSession.shared.data(for: request)
  let decoder = JSONDecoder()
  let response = try decoder.decode(MessageResponse.self, from: data)
  if let randomMessage = response.messages.randomElement() {
      return "\(randomMessage.text)"
  } else {
      return "No messages found."
  }
}

通过遵循这些步骤,您可以构建一个具有完整认证流程的安全iOS应用,包括MFA支持、令牌管理和后端API集成。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计