| title | Add sign in and sign out in native iOS/macOS app |
|---|---|
| description | Learn how to add sign-in and sign-out with email one-time passcode or username and password in iOS/macOS app by using native authentication. |
| author | henrymbuguakiarie |
| manager | pmwongera |
| ms.author | henrymbugua |
| ms.service | identity-platform |
| ms.subservice | external |
| ms.topic | tutorial |
| ms.date | 03/10/2026 |
| ms.custom |
[!INCLUDE applies-to-external-only]
This tutorial demonstrates how to sign-in and sign-out a user with email one-time passcode or username and password in your iOS/macOS app by using native authentication.
In this tutorial, you:
[!div class="checklist"]
- Sign in a user using email one-time passcode or username (email) and password.
- Sign out a user.
- Handle sign-in error
- Tutorial: Prepare your iOS/macOS app for native authentication.
- If you want to sign in using Email with password, configure your user flow to use Email with password when you create your sign-up and sign-in user flow.
To sign in a user using the Email one-time passcode flow, capture the email and send an email containing a one-time passcode for the user to verify their email. When the user enters a valid one-time passcode, the app signs them in.
To sign in a user using the Email with password flow, capture the email and password. If the username and password are valid, the app signs in the user.
To sign in a user, you need to:
-
Create a user interface (UI) to:
- Collect an email from the user. Add validation to your inputs to make sure the user enters a valid emails address.
- Collect a password if you sign in with username (email) and password.
- Collect an email one-time passcode from the user if you sign in with email one-time passcode.
- Add a button to let the user resend one-time passcode if you sign in with email one-time passcode.
-
In your UI, add a button, whose select event starts a sign-in as shown in the following code snippet:
@IBAction func signInPressed(_: Any) { guard let email = emailTextField.text else { resultTextView.text = "email not set" return } let parameters = MSALNativeAuthSignInParameters(username: email) nativeAuth.signIn(parameters: parameters, delegate: self) }
To sign in a user using Email one-time passcode flow, we use the following code snippet:
nativeAuth.signIn(parameters: parameters, delegate: self)
The
signIn(parameters:delegate)method, which responds asynchronously by calling one of the methods on the passed delegate object, must implement theSignInStartDelegateprotocol. We pass an instance ofMSALNativeAuthSignInParameterswhich contains the email address that the user provides in the email submission form and passselfas the delegate.To sign in a user using Email with password flow, we use the following code snippet:
let parameters = MSALNativeAuthSignInParameters(username: email) parameters.password = password nativeAuth.signIn(parameters: parameters, delegate: self)
In the
signIn(parameters:delegate)method, you pass an instance ofMSALNativeAuthSignInParameterswhich contains the email address that the user supplied us with and their password, alongside with the delegate object that conforms to theSignInStartDelegateprotocol. For this example, we passself. -
To implement
SignInStartDelegateprotocol when you use Email one-time passcode flow, use the following code snippet:extension ViewController: SignInStartDelegate { func onSignInStartError(error: MSAL.SignInStartError) { resultTextView.text = "Error signing in: \(error.errorDescription ?? "no description")" } func onSignInCodeRequired( newState: MSAL.SignInCodeRequiredState, sentTo: String, channelTargetType: MSAL.MSALNativeAuthChannelType, codeLength: Int ) { resultTextView.text = "Verification code sent to \(sentTo)" } }
The
signIn(parameters:delegate)results in a call to delegate methods. In the most common scenario,onSignInCodeRequired(newState:sentTo:channelTargetType:codeLength)is called to indicate that a code has been sent to verify the user's email address. Along with some details of where the code has been sent, and how many digits it contains, this delegate method also has anewStateparameter of typeSignInCodeRequiredState, which gives us access to the following two new methods:submitCode(code:delegate)resendCode(delegate)
Use
submitCode(code:delegate)to submit the one-time passcode that user supplies in one-time passcode form, use the following code snippet:newState.submitCode(code: userSuppliedCode, delegate: self)
The
submitCode(code:delegate)accepts the one-time passcode and delegate parameter. After submitting the code, you must verify the one-time passcode by implementing theSignInVerifyCodeDelegateprotocol.To implement
SignInVerifyCodeDelegateprotocol as an extension to your class, use the following code snippet:extension ViewController: SignInVerifyCodeDelegate { func onSignInVerifyCodeError(error: MSAL.VerifyCodeError, newState: MSAL.SignInCodeRequiredState?) { resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")" } func onSignInCompleted(result: MSALNativeAuthUserAccountResult) { resultTextView.text = "Signed in successfully." let parameters = MSALNativeAuthGetAccessTokenParameters() result.getAccessToken(parameters: parameters, delegate: self) } }
In the most common scenario, we receive a call to
onSignInCompleted(result)indicating that the user has signed in. The result can be used to retrieve theaccess token.The
getAccessToken(parameters:delegate)accepts aMSALNativeAuthGetAccessTokenParametersinstance and a delegate parameter and we must implement the required methods in theCredentialsDelegateprotocol.In the most common scenario, we receive a call to
onAccessTokenRetrieveCompleted(result)indicating that the user obtained anaccess token.extension ViewController: CredentialsDelegate { func onAccessTokenRetrieveError(error: MSAL.RetrieveAccessTokenError) { resultTextView.text = "Error retrieving access token" } func onAccessTokenRetrieveCompleted(result: MSALNativeAuthTokenResult) { resultTextView.text = "Signed in. Access Token: \(result.accessToken)" } }
-
To implement
SignInStartDelegateprotocol when you use Email with password flow, use the following code snippet:extension ViewController: SignInStartDelegate { func onSignInStartError(error: MSAL.SignInStartError) { resultTextView.text = "Error signing in: \(error.errorDescription ?? "no description")" } func onSignInCompleted(result: MSAL.MSALNativeAuthUserAccountResult) { // User successfully signed in } }
In the most common scenario, we receive a call to
onSignInCompleted(result)indicating that the user has signed in. The result can be used to retrieve theaccess token.The
getAccessToken(parameters:delegate)accepts aMSALNativeAuthGetAccessTokenParametersinstance and a delegate parameter and we must implement the required methods in theCredentialsDelegateprotocol.In the most common scenario, we receive a call to
onAccessTokenRetrieveCompleted(result)indicating that the user obtained anaccess token.extension ViewController: CredentialsDelegate { func onAccessTokenRetrieveError(error: MSAL.RetrieveAccessTokenError) { resultTextView.text = "Error retrieving access token" } func onAccessTokenRetrieveCompleted(result: MSALNativeAuthTokenResult) { resultTextView.text = "Signed in. Access Token: \(result.accessToken)" } }
In scenarios such as companion device support (for example, Apple Watch), your app might need a trusted component to refresh access tokens independently.
By default, MSAL manages refresh tokens internally and doesn’t return them to application code. When your scenario requires it, you can explicitly request a refresh token after sign‑in.
Important
Refresh tokens are long‑lived credentials. Only request a refresh token when your app requires it, and store and transmit it securely.
MSAL disables refresh token return by default. To request a refresh token, set returnRefreshToken to true when you acquire tokens.
let parameters = MSALNativeAuthGetAccessTokenParameters()
parameters.returnRefreshToken = true
result.getAccessToken(parameters: parameters, delegate: self)If you don’t set returnRefreshToken to true, MSAL doesn’t return a refresh token.
After token acquisition completes, read the refresh token from the token result.
func onAccessTokenRetrieveCompleted(result: MSALNativeAuthTokenResult) {
let accessToken = result.accessToken
let refreshToken = result.refreshToken
}When you enable refresh token access, follow these guidelines:
- Store refresh tokens in encrypted, platform‑protected storage.
- Avoid logging or exporting refresh tokens.
- Minimize refresh token copies across devices.
- Remove refresh tokens from all devices when the user signs out.
During sign in, not every action succeeds. For example, the user might try to sign in with an email address that doesn't exist, or submit an invalid code.
-
To handle errors in the
signIn(parameters:delegate)method, use the following code snippet:func onSignInStartError(error: MSAL.SignInStartError) { if error.isUserNotFound || error.isInvalidUsername { resultTextView.text = "Invalid username" } else { resultTextView.text = "Error signing in: \(error.errorDescription ?? "no description")" } }
-
To handle errors in
submitCode()method, use the following code snippet:func onSignInVerifyCodeError(error: MSAL.VerifyCodeError, newState: MSAL.SignInCodeRequiredState?) { if error.isInvalidCode { // Inform the user that the submitted code was incorrect and ask for a new code to be supplied let userSuppliedCode = retrieveNewCode() newState?.submitCode(code: userSuppliedCode, delegate: self) } else { resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")" } }
If the user enters an incorrect email verification code, the error handler includes a reference to a
SignInCodeRequiredStatethat can be used to submit an updated code. In our earlier implementation ofSignInVerifyCodeDelegateprotocol, we simply displayed the error when we handled theonSignInVerifyCodeError(error:newState)delegate function.
Once your app acquires an ID token, you can retrieve the claims associated with the current account. To do so, use the following code snippet:
func onSignInCompleted(result: MSAL.MSALNativeAuthUserAccountResult) {
let claims = result.account.accountClaims
let preferredUsername = claims?["preferred_username"] as? String
}The key you use to access the claim value is the name that you specify when you add the user attribute as a token claim.
Learn how to add built-in and custom attributes as token claims in the Add user attributes to token claims article.
To sign out a user, use the reference to the MSALNativeAuthUserAccountResult that you received in the onSignInCompleted callback, or use getNativeAuthUserAccount() to get any signed in account from the cache and store a reference in the accountResult member variable.
-
Configure the keychain group for your project as described here.
-
Add a new member variable to your
ViewControllerclass:var accountResult: MSALNativeAuthUserAccountResult?. -
Update
viewDidLoadto retrieve any cached account by adding this line afternativeAuthis initialized successfully:accountResult = nativeAuth.getNativeAuthUserAccount(). -
Update the
signInCompletedhandler to store the account result:func onSignInCompleted(result: MSALNativeAuthUserAccountResult) { resultTextView.text = "Signed in successfully" accountResult = result }
-
Add a Sign Out button and use the following code to sign out user:
@IBAction func signOutPressed(_: Any) { guard let accountResult = accountResult else { print("Not currently signed in") return } accountResult.signOut() self.accountResult = nil resultTextView.text = "Signed out" }
You have successfully completed all the necessary steps to sign out a user on your app. Build and run your application. If all good, you should be able to select sign out button to successfully sign out.
[!INCLUDE Enable sign-in with an alias or username]
[!INCLUDE Custom claims provider]