Tutorial: Add sign in and sign out with email one-time passcode
This tutorial demonstrates how to sign in and sign out a user using email one-time passcode (OTP) in your native auth iOS Swift app.
In this tutorial, you learn how to:
- Sign in user.
- Sign out user.
Prerequisites
Sign in user
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 and displays the account details.
To sign in user using Email one-time-passcode you need to:
Create your user interface that includes:
- A form to submit an Email.
- A form to submit one-time passcode.
- A page to display the account details.
To sign in user, we're going to use
signIn(username:delegate)
method, which responds asynchronously by calling one of the methods on the passed delegate object, which must implement theSignInStartDelegate
protocol. To implement thesignIn(username:delegate)
, use the following code snippet:nativeAuth.signIn(username: email, delegate: self)
We pass the email address that the user provides in the email submission form and pass
self
as the delegate.To implement
SignInStartDelegate
protocol as an extension to your class, 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(username: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 anewState
parameter of typeSignInCodeRequiredState
, which gives us access to the following two new methods:submitCode(code:delegate)
resendCode(delegate)
To 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 theSignInVerifyCodeDelegate
protocol.To implement
SignInVerifyCodeDelegate
protocol 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." result.getAccessToken(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(delegate)
accepts a delegate parameter and we must implement the required methods in theCredentialsDelegate
protocol.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)" } }
Handle errors during sign in
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.
In our earlier implementation of SignInStartDelegate
protocol, we simply displayed the error when we handled the onSignInStartError(error)
delegate function.
To enhance the user experience by managing the particular error type, 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")"
}
}
If the user enters an incorrect email verification code, the error handler includes a reference to a SignInCodeRequiredState
that can be used to submit an updated code. In our earlier implementation of SignInVerifyCodeDelegate
protocol, we simply displayed the error when we handled the onSignInVerifyCodeError(error:newState)
delegate function.
For a better user experience when an incorrect one-time passcode is entered, use the following code snippet to prompt for the correct code and request resubmission:
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")"
}
}
Well, you have done everything that is required to successfully sign in a user on your app. Build and run your application. If all good, you should be able to provide an email ID, receive a code on the email, and use that to successfully sign in user.
Sign out user
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
ViewController
class:var accountResult: MSALNativeAuthUserAccountResult?
.Update
viewDidLoad
to retrieve any cached account by adding this line afternativeAuth
is initialized successfully:accountResult = nativeAuth.getNativeAuthUserAccount()
.Update the
signInCompleted
handler 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.
Next steps
Tutorial: Add built-in attributes to your native iOS authentication.
Feedback
https://aka.ms/ContentUserFeedback.
Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see:Submit and view feedback for