Tutorial: Add sign-up in an iOS/macOS app using native authentication
Applies to: iOS (Swift) macOS (Swift)
This tutorial demonstrates how to sign up a user using email one-time passcode or username (email) and password, and collects user attributes in your iOS/macOS app using native authentication.
- Sign up a user by using email one-time passcode or username (email) and password.
- Collect user attributes during sign-up.
- Handle sign-up errors.
Prerequisites
- Tutorial: Prepare your iOS/macOS app for native authentication.
- If you want to collect user attributes during sign-up, configure the user attributes when you create your sign-up and sign-in user flow.
Sign up a user
To sign up a user using the email one-time passcode or username (email) and password, you collect an email from the user, then send an email containing an email one-time passcode to the user. The user enters a valid email one-time passcode to validate their username.
To sign up 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 email address.
- Collect a password if you sign up with username (email) and password.
- Collect an email one-time passcode from the user.
- If needed, collect user attributes.
- Resend one-time passcode if the user doesn't receive it.
- Start sign-up flow.
In your app, add a button, whose select event triggers the following code snippet:
@IBAction func signUpPressed(_: Any) { guard let email = emailTextField.text else { resultTextView.text = "Email or password not set" return } nativeAuth.signUp(username: email, delegate: self) }
To sign up a user using Email one-time-passcode, we use the library's
signUp(username:delegate)
method, which responds asynchronously by calling one of the methods on the passed delegate object, which must implement theSignUpStartDelegate
protocol. The following line of code initiates the user sign-up process:nativeAuth.signUp(username: email, delegate: self)
In the
signUp(username:delegate)
method, we pass the user's email address from the submission form and the delegate (a class that implements theSignUpStartDelegate
protocol).To sign up a user using Email with password, use the following code snippets:
@IBAction func signUpPressed(_: Any) { guard let email = emailTextField.text, let password = passwordTextField.text else { resultTextView.text = "Email or password not set" return } nativeAuth.signUp(username: email,password: password,delegate: self) }
we use the library's
signUp(username:password:delegate)
method, which responds asynchronously by calling one of the methods on the passed delegate object, which must implement theSignUpStartDelegate
protocol. The following line of code initiates the user sign-up process:nativeAuth.signUp(username: email, password: password, delegate: self)
In the
signUp(username:password:delegate)
method, we pass the user's email address, their password, and the delegate (a class that implements theSignUpStartDelegate
protocol).To implement
SignUpStartDelegate
protocol as an extension to our class, use:extension ViewController: SignUpStartDelegate { func onSignUpStartError(error: MSAL.SignUpStartError) { resultTextView.text = "Error signing up: \(error.errorDescription ?? "no description")" } func onSignUpCodeRequired( newState: MSAL.SignUpCodeRequiredState, sentTo: String, channelTargetType: MSAL.MSALNativeAuthChannelType, codeLength: Int ) { resultTextView.text = "Verification code sent to \(sentTo)" } }
The call to
signUp(username:password:delegate)
orsignUp(username:delegate)
results in a call to eitheronSignUpCodeRequired()
oronSignUpStartError()
delegate methods. TheonSignUpCodeRequired(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 typeSignUpCodeRequiredState
, which gives us access to two new methods:submitCode(code:delegate)
resendCode(delegate)
To submit the code that the user supplied us with, use:
newState.submitCode(code: userSuppliedCode, delegate: self)
To implement
SignUpVerifyCodeDelegate
protocol as an extension to our class, use:extension ViewController: SignUpVerifyCodeDelegate { func onSignUpVerifyCodeError(error: MSAL.VerifyCodeError, newState: MSAL.SignUpCodeRequiredState?) { resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")" } func onSignUpCompleted(newState: SignInAfterSignUpState) { resultTextView.text = "Signed up successfully!" } }
The
submitCode(code:delegate)
accepts a delegate parameter and we must implement the required methods in theSignUpVerifyCodeDelegate
protocol. In the most common scenario, we receive a call toonSignUpCompleted(newState)
indicating that the user has been signed up and the flow is complete.
Collect user attributes during sign-up
Whether you sign up a user using email one-time passcode or username (email) and password, you can collect user attributes before a user's account is created. The signUp(username:attributes:delegate)
method, accepts attributes as a parameter.
To collect user attributes, use the following code snippet:
let attributes = [ "country": "United States", "city": "Redmond" ] nativeAuth.signUp(username: email, attributes: attributes, delegate: self)
The
signUp(username:attributes:delegate)
orignUp(username:password:attributes:delegate)
results in a call to eitheronSignUpCodeRequired()
oronSignUpStartError()
delegate methods, or in a call toonSignUpAttributesInvalid(attributeNames: [String])
if it's implemented in the delegate.To implement the
SignUpStartDelegate
protocol as an extension to our class, use the following code snippet:extension ViewController: SignUpStartDelegate { func onSignUpStartError(error: MSAL.SignUpStartError) { resultTextView.text = "Error signing up: \(error.errorDescription ?? "no description")" } func onSignUpCodeRequired( newState: MSAL.SignUpCodeRequiredState, sentTo: String, channelTargetType: MSAL.MSALNativeAuthChannelType, codeLength: Int ) { resultTextView.text = "Verification code sent to \(sentTo)" } func onSignUpAttributesInvalid(attributeNames: [String]) { resultTextView.text = "Invalid attributes \(attributeNames)" } }
If the attributes are invalid, the method
onSignUpAttributesInvalid(attributeNames: [String])
is called. In this case, we display the list of invalid attributes to the user. Otherwise, theonSignUpCodeRequired(newState:sentTo:channelTargetType:codeLength)
is called to indicate that a code has been sent to verify the user's email address. Apart from details such as the recipient of the code, and number of digits of the code, this delegate method has anewState
parameter of typeSignUpCodeRequiredState
, which gives us access to two new methods:submitCode(code:delegate)
resendCode(delegate)
User attributes across one or more pages
To spread the attributes across one or more pages, we must set the attributes we intend to collect across different pages as mandatory in the customer identity and access management (CIAM) tenant configuration.
We call signUp(username:password:delegate)
without passing any attributes. The next step will be to call newState.submitCode(code: userSuppliedCode, delegate: self)
to verify user's email.
We implement the SignUpVerifyCodeDelegate
protocol as an extension to our class as before, but this time we must implement the optional method onSignUpAttributesRequired(attributes:newState)
in addition to the required methods:
extension ViewController: SignUpVerifyCodeDelegate {
func onSignUpAttributesRequired(newState: SignUpAttributesRequiredState) {
resultTextView.text = "Attributes required"
}
func onSignUpVerifyCodeError(error: MSAL.VerifyCodeError, newState: MSAL.SignUpCodeRequiredState?) {
resultTextView.text = "Error verifying code: \(error.errorDescription ?? "no description")"
}
func onSignUpCompleted(newState: SignInAfterSignUpState) {
resultTextView.text = "Signed up successfully!"
}
}
This delegate method has a newState
parameter of type SignUpAttributesRequiredState
, which gives us access to a new method:
submitAttributes(attributes:delegate)
To submit the attributes that the user supplied us with, use the following code snippet:
let attributes = [
"country": "United States",
"city": "Redmond"
]
newState.submitAttributes(attributes: attributes, delegate: self)
We'll also implement the SignUpAttributesRequiredDelegate
protocol as an extension to our class:
extension ViewController: SignUpAttributesRequiredDelegate {
func onSignUpAttributesRequiredError(error: AttributesRequiredError) {
resultTextView.text = "Error submitting attributes: \(error.errorDescription ?? "no description")"
}
func onSignUpAttributesRequired(attributes: [MSALNativeAuthRequiredAttribute], newState: SignUpAttributesRequiredState) {
resultTextView.text = "Attributes required"
}
func onSignUpAttributesInvalid(attributeNames: [String], newState: SignUpAttributesRequiredState) {
resultTextView.text = "Attributes invalid"
}
func onSignUpCompleted(newState: SignInAfterSignUpState) {
resultTextView.text = "Signed up successfully!"
}
}
When the user doesn't provide all the required attributes, or the attributes are invalid, these delegate methods are called:
onSignUpAttributesInvalid
: indicates that one or more attributes that were sent failed input validation. This error contains an attributeNames parameter, which is a list of all attributes that were sent by the developer that failed input validation.onSignUpAttributesRequired
: indicates that the server requires one or more attributes to be sent, before the user account can be created. This happens when one or more attributes is set as mandatory in the tenant configuration. This result contains attributes parameter, which is a list ofMSALNativeAuthRequiredAttribute
objects, which outline details about the user attributes that the API requires.
Both delegate methods contain a new state reference. We use the newState
parameter to call submitAttributes(attributes:delegate)
again with the new attributes.
Handle sign-up errors
During sign-up, not every action succeeds. For example, the user might try to sign up with an email address that's already in use, or submit an invalid code.
In our earlier implementation of SignUpStartDelegate
protocol, we simply displayed the error when we handled the onSignUpStartError(error)
delegate function.
To enhance the user experience by managing the particular error type, use the following code snippet:
func onSignUpStartError(error: MSAL.SignUpStartError) {
if error.isUserAlreadyExists {
resultTextView.text = "Unable to sign up: User already exists"
} else if error.isInvalidPassword {
resultTextView.text = "Unable to sign up: The password is invalid"
} else if error.isInvalidUsername {
resultTextView.text = "Unable to sign up: The username is invalid"
} else {
resultTextView.text = "Unexpected error signing up: \(error.errorDescription ?? "no description")"
}
}
Optional: Sign in after a sign-up flow
After a successful sign-up flow, you can sign-in a user without initiating a sign-in flow. Learn more in the Tutorial: Sign in user automatically after sign-up in an iOS/macOS app article.