Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A Sample App working with SwiftUI framework will help tremendously #47

Closed
rajahimanath opened this issue Jan 23, 2021 · 23 comments
Closed
Assignees

Comments

@rajahimanath
Copy link

No description provided.

@antrix1989 antrix1989 self-assigned this Jan 29, 2021
@antrix1989
Copy link
Contributor

Thanks for the request. I will update here once we add a sample with SwiftUi.

@iShaymus
Copy link

+1 On this. I've just written an entire application in pure SwiftUI just went to add the Auth only to find that there is no examples for it at all. I probably should have looked at this first XD

@arbyruns
Copy link

+1 to this. As a new dev learning SwiftUI I’m finding it difficult to implement a way to do this.

@osca2000
Copy link

+1 mil to this from me. waiting for almost a year now.

@chiyichu
Copy link

chiyichu commented Mar 5, 2021

+1 +1 to this. I tried using SwiftUI protocol UIViewControllerRepresentable to 'wrap' an UIViewController in a SwiftUI page view, but eventually got lost on how to pass it to MSALWebviewParameters. A SwiftUI example will definitely help a lot.

@Prabhakaran-Ganesan
Copy link

Prabhakaran-Ganesan commented Mar 7, 2021

+1 to this.
It will be very helpful as it is a high priority requirement for us..
Referring another thread here

@DanielsCode
Copy link

+1

@JeaNugroho
Copy link

+1 to this too!

I am currently working on a senior design capstone project, and this sample app or tutorial would be very beneficial!

@BoobalakannanS
Copy link

+1 to this..

@MatteCarra
Copy link

MatteCarra commented Aug 12, 2021

I have experimented with this a little bit.
I thought I found solution with both the browser and ms authenticator app without even touching UIKit, but it has some problems.
As you may know to support the ms authenticator app you need to have either an AppDelegate or SceneDelegate to handle the open url.
You can have an AppDelegate with swiftui (@UIApplicationDelegateAdaptor in the main App) but the open url method is never called.
The only way that I found to make it work is by using the SwiftUI onOpenUrl method and call MSALPublicClientApplication.handleMSALResponse(url) from there.
There are two problems with this approach:

  • handleMSALResponse(URL) method is deprecated, and it tells to instead call the handleMSALResponse(URL, sourceApplication: String?). sourceApplication is not provided by SwiftUI's onOpenUrl callback.
  • I get a runtime error after the ms authenticator app returns to my app that says: "SwiftUI: Cannot use Scene methods for URL, NSUserActivity, and other External Events without using SwiftUI Lifecycle. Without SwiftUI Lifecycle, advertising and handling External Events wastes resources, and will have unpredictable results."

Let me know if you have found a different solution.

P.S: the msContext is initialised in the @main app constructor by simply following the UIKit guide.

Button {
    if let view = UIApplication.shared.windows.first?.rootViewController {
        let parameters = MSALInteractiveTokenParameters(
            scopes: ["XboxLive.signin"],
            webviewParameters: MSALWebviewParameters(
                authPresentationViewController: view
            )
        )
        
        //Modify the ui with the progress indicator
        
        msContext.acquireToken(with: parameters) {
            (result, ex) in
            if let exception = ex {
                print("MS auth error: ", exception)
                //remove the progress indicator and show the error
                return
            }
            
            guard let msToken = result?.accessToken, let id = result?.account.identifier else {
                 //remove the progress indicator and show an error
                return
            }
            
            //Go ahead and use your id and msToken for whatever you need
        }
    }
} label: {
    HStack(spacing: 5) {
        Spacer()
        
        Image("microsoft_signin")
        
        Text("Sign in with Microsoft")
            .foregroundColor(.primary)
            .font(.system(size: 15, weight: .semibold))
        
        Spacer()
    }
}.onOpenURL(perform: { url in
    MSALPublicClientApplication.handleMSALResponse(url)
})

@alschmut
Copy link

I was trying to solve a similar issue the other day, creating a SwiftUI based app from another MS Sample Project https://github.com/Azure-Samples/ms-identity-mobile-apple-swift-objc.

I uploaded my SwiftUI sample here https://github.com/alschmut/MSALSwiftUI. From what I have tested until now, it seems to work fine with the MS Authenticator App. Feel free to have a look :)

@ljunquera
Copy link

ljunquera commented Jan 12, 2022

+1 for this request. I did see a post in StackOverflow with a similar request (which did get a response), but they all seem to be working around the problem.

https://stackoverflow.com/questions/70654875/im-trying-to-convert-a-msal-login-from-uikit-to-swiftui-and-not-sure-how-i-can

@arbyruns
Copy link

+1 for this request. I did see a post in StackOverflow with a similar request (which did get a response), but they all seem to be working around the problem.

https://stackoverflow.com/questions/70654875/im-trying-to-convert-a-msal-login-from-uikit-to-swiftui-and-not-sure-how-i-can

That's me! Here's the project.

@ljunquera
Copy link

ljunquera commented Jan 12, 2022

@arbyruns, have you looked at the @alschmut example which encapsulates all the authentication? I did this all in Android and am now trying to make it work in Swift but am a beginner.

I see the call in @arbyruns:
let webViewParameters = MSALWebviewParameters(authPresentationViewController: self)

And in @alschmut's example:
self.webViewParamaters = MSALWebviewParameters(authPresentationViewController: topViewController)

In @arbyruns it's called within a UIViewController class and his uses:

private func topViewController() -> UIViewController? {
    let window = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
    let rootVC = window?.rootViewController
    return rootVC?.top()
}

It seems like you should be able to abstract it from the UI, but need a callback/observable. Is there something Microsoft needs to do to enable this so we don't need an actual UIViewController in the call anywhere?

@ljunquera
Copy link

@ljunquera
Copy link

Since this request has been outstanding for so long, I'm concerned about the level of support moving forward. Maybe I should consider generic oAuth Swift options as B2C supports oAuth2, like https://github.com/OAuthSwift/OAuthSwift and https://github.com/p2/OAuth2

@juan-arias
Copy link
Contributor

This is also discussed on the MSAL repository where a sample has been shared: AzureAD/microsoft-authentication-library-for-objc#1437 (comment)

@ljunquera
Copy link

@antrix1989, how are we doing with this?

@antrix1989
Copy link
Contributor

Closed as duplicate.

@andrejandre
Copy link

Status? Can't find duplicate nor any examples other than student pet projects

@shinyion
Copy link

@antrix1989 "Closed as duplicate."
It would be very helpful to post a link to the original post, so we could follow it.

@PMCS64
Copy link

PMCS64 commented Mar 15, 2024

There was a code from @arbyruns which fitted the bill for me (source here), however it is compatible on older versions of iOS. Here is an updated version for iOS17:

import SwiftUI
import UIKit
import Foundation
import MSAL


class MSALScreenViewModel: ObservableObject, MSALScreenViewModelProtocol{

    var uiViewController: MSALScreenViewControllerProtocol? = nil
    @Published var accountName: String = ""
    @Published var scopes: [String] = []

    func loadMSALScreen() {
        print(#function)
        uiViewController?.loadMSALScreen()
    }

    func getAccountName() -> String {
        print(#function)
        return accountName
    }

}

struct MSALScreenView_UI: UIViewControllerRepresentable{
    @ObservedObject var viewModel: MSALScreenViewModel
    func makeUIViewController(context: Context) -> some MSALScreenViewController {
        print(#function)
        return MSALScreenViewController(viewModel: viewModel)
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        print(#function)
    }
}

class MSALScreenViewController: UIViewController, MSALScreenViewControllerProtocol {
    let kClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    let kRedirectUri = "msauth.YourProject://auth"
    let kScopes: [String] = ["User.Read"]
    let kAuthority = "https://login.microsoftonline.com/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"

    var uiViewController: MSALScreenViewModelProtocol?
    var viewModel: MSALScreenViewModelProtocol

    init(viewModel: MSALScreenViewModelProtocol) {
        print(#function)
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: .main)
        self.viewModel.uiViewController = self
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        print(#function)
        viewModel.loadMSALScreen()
    }

    func loadMSALScreen() {
        let msalModel = MSALScreenViewModel()

        do {
            let authority = try MSALAuthority(url: URL(string: kAuthority)!)
            let pcaConfig = MSALPublicClientApplicationConfig(clientId: kClientId, redirectUri: kRedirectUri, authority: authority)
            let application = try MSALPublicClientApplication(configuration: pcaConfig)
            let webViewParameters = MSALWebviewParameters(authPresentationViewController: self)
            let interactiveParameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters)
            application.acquireToken(with: interactiveParameters) { (result, error) in
                guard let result = result else {
                    if let error = error {
                        print("Error: \(error.localizedDescription)")
                    } else {
                        print("Unknown error occurred")
                    }
                    return
                }
                if let account = result.account.username {
                    msalModel.accountName = account
                    msalModel.scopes = result.scopes
                    if let keyWindow = UIApplication.shared.currentUIWindow() {
                        keyWindow.rootViewController = UIHostingController(rootView: MicrosoftView())
                    }
                }
            }
        } catch {
            print("\(#function) logging error \(error)")
        }
    }
}

protocol MSALScreenViewModelProtocol{
    var uiViewController: MSALScreenViewControllerProtocol? { get set }
    func loadMSALScreen()
    func getAccountName() -> String
}

protocol MSALScreenViewControllerProtocol: UIViewController{
    var viewModel: MSALScreenViewModelProtocol { get set }
    func loadMSALScreen()
}

struct MicrosoftView: View {
    @StateObject var msalModel: MSALScreenViewModel = MSALScreenViewModel()
    @State private var isLoggedIn: Bool = false

    var body: some View {
        NavigationStack {
            VStack {
                Spacer()
                if isLoggedIn {
                    List {
                        NavigationLink(
                            destination: YourHomeView()
                        ) {
                            HStack {
                                Text("Access Home")
                                    .font(.subheadline)
                                    .fontWeight(.bold)
                            }
                        }
                    }
                } else {
                    Button("Login with Microsoft 365") {
                        msalModel.loadMSALScreen()
                    }
                    MSALScreenView_UI(viewModel: msalModel)
                        .frame(width: 250, height: 250, alignment: .center)
                }
            }
            .onReceive(msalModel.$accountName) { accountName in
                isLoggedIn = !accountName.isEmpty
            }
        }
    }
}

public extension UIApplication {
    func currentUIWindow() -> UIWindow? {
        let connectedScenes = UIApplication.shared.connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .compactMap { $0 as? UIWindowScene }
        
        let window = connectedScenes.first?
            .windows
            .first { $0.isKeyWindow }

        return window
    }
}

#Preview {
    MicrosoftView()
}

@johnlabrookstonecm
Copy link

Closed as duplicate.

@antrix1989 Can you please link the duplicate post? If there is no post, is there an update on a full SwiftUI implementation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests