User Registration with AWS Cognito on iOS (without Amplify)
If you’re just getting started with AWS Cognito on iOS, you’ve probably asked yourself: how do I implement a user registration system from scratch? In this article, we’ll build a registration feature step by step using the AWS Cognito SDK directly — no Amplify involved.
As always, we’ll keep the implementation clear, simple, and approachable. We won’t focus on complex architectures (MVVM, Clean Architecture, etc.), but on understanding how Cognito works under the hood.
⚠️ Important note about validations
In this article (and throughout the series), our main focus is learning how to integrate and use AWS Cognito — not implementing every iOS best practice. For that reason:
- We won’t implement exhaustive email format validations (complex regex, domain verification, etc.)
- We won’t add advanced password validations beyond the basics (length, special characters, etc.)
- We won’t implement elaborate error handling with custom messages for every case
- We won’t use complex architectures (MVVM, VIPER, Clean Architecture)
This doesn’t mean these validations aren’t important. In a production app, you absolutely should implement them. We simply skip them here to stay focused on our goal: understanding how AWS Cognito works and how to integrate it correctly.
If you want to learn about validations, architectures, and iOS best practices, there are excellent resources dedicated to those topics. Here we’re 100% focused on AWS.
What are we going to build?
A simple registration screen that allows the user to:
- Enter email
- Enter password
- Confirm password
- Tap a register button
For now, we’ll only build the UI. In the following steps we’ll add the real Cognito functionality.
Prerequisites
- Xcode installed (version 15 or higher)
- Basic knowledge of SwiftUI
- An AWS account (we’ll set it up later when we connect Cognito)
Step 1: Building the registration UI
First, let’s create a simple, clean view for user registration. Create a file called RegisterView.swift:
import SwiftUI
struct RegisterView: View {
@State private var email: String = ""
@State private var password: String = ""
@State private var confirmPassword: String = ""
var body: some View {
VStack(spacing: 20) {
// Title
Text("Create Account")
.font(.largeTitle)
.fontWeight(.bold)
.padding(.bottom, 30)
// Email field
TextField("Email", text: $email)
.textInputAutocapitalization(.never)
.keyboardType(.emailAddress)
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
// Password field
SecureField("Password", text: $password)
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
// Confirm password field
SecureField("Confirm Password", text: $confirmPassword)
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
// Register button
Button(action: {
// Registration logic will go here
print("Register user")
}) {
Text("Sign Up")
.fontWeight(.semibold)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
.padding(.top, 20)
Spacer()
}
.padding(.horizontal, 30)
.padding(.top, 50)
}
}
What does this code do?
- Local states (@State):
email,password, andconfirmPasswordstore the form values - UI elements: TextField for email (with email keyboard and no autocapitalization), SecureField for passwords (hides text), Button that for now only prints to console
- Design: VStack with 20pt spacing, light gray background for fields, 10pt corner radius, blue button spanning the full width
Step 2: Displaying the view
Now update ContentView.swift to show our registration view:
import SwiftUI
struct ContentView: View {
var body: some View {
RegisterView()
}
}
Simple and straightforward. For now, ContentView just displays the registration view.
Result so far
If you run the app in the simulator, you’ll see a clean screen with a «Create Account» title, an email field, a password field, a confirm password field, and a «Sign Up» button. The button doesn’t do anything yet — that’s exactly what we want for now.
Step 3: Setting up the AWS Cognito User Pool
Now that our UI is ready, we need to create the «backend» where our users will be stored. We’ll use an AWS Cognito User Pool for this.
3.1 Access the AWS Cognito console
- Go to https://console.aws.amazon.com/
- Sign in with your AWS account
- Type «Cognito» in the top search bar
- Click on «Amazon Cognito»
3.2 Create a new User Pool
On the main Cognito screen, click «Create user pool». AWS Cognito will show you different options depending on the type of application you’re building. You’ll see 4 app type options:
- Traditional web application
- Single-page application
- Mobile app ← Select this one
- Machine-to-machine application
Select «Mobile app». This template automatically configures the User Pool with the correct settings for mobile apps, such as not generating a «client secret» — which is important for security in native mobile apps.
3.3 Configure the application name
In the name field, enter a name for your application (e.g., ios-tutorial-sgdgjz). You can use any name, but make it descriptive. Then click «Next».
3.4 Configure authentication options
AWS will ask how you want users to sign in. You’ll see three options: Email, Phone number, and Username. For this tutorial, select only:
- ❌ Phone number (unchecked)
- ❌ Username (unchecked)
Email is the most common and simple form of authentication. Users will sign up and log in using their email address. Let’s keep things simple to start.
3.5 Configure self-registration
- Enable self-registration: ✅ Yes, enable it — this allows anyone to create an account in your app. Without this, only an admin could create users.
- Required sign-in attributes: Only email for now. Don’t add extra attributes (name, last name, etc.). Keeping just email makes the process simpler.
3.6 Return URL (optional)
You may see an option to «Add a return URL» (optional). Leave it empty / skip it. This option is for OAuth flows or when using Hosted UI. Since we’re doing native authentication in the mobile app, we don’t need it.
3.7 Confirm creation
Click «Create». You’ll see a confirmation message and AWS will take you to a page that says «Configure your application resources». Your User Pool is now created and ready to use.
Note: AWS will show an option saying «Select a platform to see the quick setup guide». You can consult it if you want to see AWS’s official documentation, but in this article we’ll guide you step by step with more detail so you understand exactly what we’re doing and why.
3.8 What did we just create?
- A User Pool: The «container» where all your users will be stored
- An app client: The configuration that allows your iOS app to connect to that User Pool
- Authentication configuration: We defined that we’ll use email for login and allow self-registration
3.9 Getting the configuration data
We need three important pieces of data from the User Pool to use in our iOS code.
3.9.1 Getting the Pool ID and Region: In the AWS Cognito console, go to «User pools» in the left sidebar, click your User Pool, and find the User Pool ID. The format is {region}_{identifier}, for example: us-east-1_AbCd12345.
3.9.2 Getting the Client ID: On the same User Pool page, click the «App integration» tab, scroll down to «App clients», click your client name, and copy the Client ID — a long string of letters and numbers without dashes.
Region: us-east-1 (Replace with yours)
Pool ID: us-east-1_AbCd12345 (Replace with yours)
Client ID: a1b2c3d4e5f6g7h8i9j0k1l2m3 (Replace with yours)
IMPORTANT: The values shown above are fictional examples. Your real values will be different. Never share these credentials publicly or push them to GitHub.
Step 4: Understanding the two ways to integrate Cognito on iOS
Before installing the SDK, it’s important to understand that there are two main ways to integrate AWS Cognito in an iOS app. AWS will show you both options, and it’s crucial to know which one you need to avoid confusion.
4.1 Option 1: Hosted UI with OAuth (AppAuth)
Hosted UI is a pre-built web interface that AWS provides for authentication. When a user wants to sign in, your app opens an AWS web page inside a Safari View Controller where the user enters their credentials.
- ✅ No need to build a login UI (AWS provides it)
- ✅ AWS handles the security of the login page
- ✅ Easy to integrate social login (Google, Facebook, Apple) in one flow
- ❌ The login UI is a web page, not native
- ❌ Less control over design and experience
- ❌ Requires additional URL configuration and OAuth/OpenID Connect setup
4.2 Option 2: Native AWS Cognito SDK (what we’ll use)
The native SDK lets you communicate directly with AWS Cognito from your Swift code. You build the UI (TextFields, buttons, etc.) and the SDK handles sending requests to Cognito.
How it works:
- The user enters email and password in YOUR interface (built in SwiftUI/UIKit)
- Your code calls SDK methods:
signUp(),signIn(), etc. - The SDK communicates directly with Cognito servers
- Cognito responds with tokens or errors
- Your code handles the response
- ✅ Full control over UI and user experience
- ✅ 100% native interface (SwiftUI or UIKit)
- ✅ User never leaves your app
- ✅ You understand exactly what’s happening at each step
- ❌ You have to build your own login UI
- ❌ More code to write
4.3 Which one will we use in this tutorial?
We’ll use the Native AWS Cognito SDK (Option 2). Why?
- Deep learning: You’ll understand exactly how Cognito works, not just «plug and play»
- Full control: We already built a nice SwiftUI interface — why use a web page?
- Native experience: The app feels more professional and polished
- Flexibility: You can customize every aspect of the authentication flow
- Solid foundation: Once you understand the native SDK, Hosted UI will be trivial if you ever need it
4.4 Don’t confuse the AWS guides
When you look at AWS documentation or the quick setup guides, AWS shows steps for both options. If you see instructions mentioning ❌ AppAuth-iOS, ❌ Carthage, ❌ callback/redirect URLs, ❌ Hosted UI, or ❌ OAuth flows → those are for Option 1. Don’t follow them.
What we need: ✅ AWSCognitoIdentityProvider, ✅ Swift Package Manager, ✅ Pool ID and Client ID, ✅ Methods like signUp(), signIn(), confirmSignUp()
Step 5: Installing the AWS Cognito SDK
We’ll use Swift Package Manager (SPM) because it’s already built into Xcode — no extra tools needed — and it’s the modern, Apple-recommended approach.
Note about AWS SDKs: AWS has two SDKs for iOS: the classic SDK (Objective-C/Swift, CocoaPods only) and the new AWS SDK for Swift (modern, 100% Swift, native SPM support). We’ll use the new one.
5.2.1 Adding the package in Xcode
- Open your project in Xcode
- Go to: File → Add Package Dependencies…
- Paste this URL in the search bar:
https://github.com/awslabs/aws-sdk-swift - Press Enter
5.2.2 GitHub authentication
Xcode will ask you to authenticate with GitHub and generate a Personal Access Token. The AWS SDK for iOS is hosted on GitHub, so Xcode needs permission to download it.
Go to GitHub → Settings → Developer settings → Personal access tokens → Generate new token (classic). Give it a descriptive name (e.g., Xcode AWS SDK Access) and select these scopes: ✅ repo, ✅ admin:public_key, ✅ write:discussion, ✅ user. Copy the token immediately (it’s only shown once) and paste it into Xcode.
IMPORTANT: Save this token somewhere safe. Never share it publicly or push it to GitHub.
5.2.3 Selecting the Cognito package
After authenticating, Xcode will show a list with many packages from the AWS SDK (S3, DynamoDB, Lambda, Cognito, etc.). For our project we only need: ✅ AWSCognitoIdentityProvider.
- Find and select
AWSCognitoIdentityProviderfrom the list - In «Add to target», select your main target (e.g.,
CognitoAuthDemo) - Click «Add Package»
Xcode will start downloading and installing the package along with its dependencies. This may take 1–2 minutes depending on your internet connection.
5.2.4 Verifying the installation
Once Xcode finishes: open the Project Navigator (left panel), you should see a new «Package Dependencies» section with aws-sdk-swift listed. The AWS Cognito SDK is now installed and ready to use.
Step 6: Implementing the registration logic
6.1 Create the authentication service
First, let’s create a file to handle all communication with AWS Cognito. This keeps the code organized and separates authentication logic from the UI.
Create a new Swift file called CognitoAuthService.swift:
import Foundation
import AWSCognitoIdentityProvider
import AWSClientRuntime
class CognitoAuthService {
// MARK: - Configuration
// Replace these values with your own from the User Pool
private let region = "us-east-1" // Your actual region
private let userPoolId = "us-east-1_XXXXX" // Your actual Pool ID (replace with yours)
private let clientId = "XXXXXXXXXXXXXXXXX" // Your actual Client ID (replace with yours)
private var cognitoClient: CognitoIdentityProviderClient?
init() {
// Initialize the Cognito client
Task {
do {
self.cognitoClient = try await CognitoIdentityProviderClient(region: region)
} catch {
print("Error initializing Cognito Client: \(error)")
}
}
}
// MARK: - User Registration
func signUp(email: String, password: String) async throws -> String {
guard let client = cognitoClient else {
throw NSError(domain: "CognitoAuthService", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Cognito client not initialized"])
}
// Create user attributes
let emailAttribute = CognitoIdentityProviderClientTypes.AttributeType(
name: "email",
value: email
)
// Create the sign-up request
let signUpInput = SignUpInput(
clientId: clientId,
password: password,
userAttributes: [emailAttribute],
username: email
)
do {
let response = try await client.signUp(input: signUpInput)
print("✅ User registered successfully")
print("User Sub: \(response.userSub ?? "N/A")")
print("Confirmed?: \(response.userConfirmed)")
if response.userConfirmed {
return "User registered and automatically confirmed"
} else {
return "User registered. Check your email for the verification code"
}
} catch {
// Handle Cognito errors
print("❌ Registration error: \(error)")
throw NSError(domain: "CognitoAuthService", code: 1000,
userInfo: [NSLocalizedDescriptionKey: "Registration error: \(error.localizedDescription)"])
}
}
}
What does this code do?
- Configuration: Defines the region, Pool ID, and Client ID (your AWS credentials)
- Initialization: Creates the Cognito client when the class is instantiated
- signUp(): Async method that registers a new user — receives email and password, creates an email attribute, sends the request to AWS Cognito, and returns a success message or throws an error
Important: Replace the values of region, userPoolId, and clientId with your real values from Step 3.
6.2 Update the registration view
Now let’s update RegisterView.swift to use our authentication service. Replace the entire file content with:
import SwiftUI
struct RegisterView: View {
@State private var email: String = ""
@State private var password: String = ""
@State private var confirmPassword: String = ""
@State private var isLoading: Bool = false
@State private var message: String = ""
@State private var showAlert: Bool = false
private let authService = CognitoAuthService()
var body: some View {
VStack(spacing: 20) {
Text("Create Account")
.font(.largeTitle)
.fontWeight(.bold)
.padding(.bottom, 30)
TextField("Email", text: $email)
.textInputAutocapitalization(.never)
.keyboardType(.emailAddress)
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
.disabled(isLoading)
SecureField("Password", text: $password)
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
.disabled(isLoading)
SecureField("Confirm Password", text: $confirmPassword)
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
.disabled(isLoading)
// Show message if it exists
if !message.isEmpty {
Text(message)
.font(.caption)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
.padding(.horizontal)
}
Button(action: { registerUser() }) {
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.frame(maxWidth: .infinity)
.padding()
} else {
Text("Sign Up")
.fontWeight(.semibold)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
}
}
.background(isLoading ? Color.gray : Color.blue)
.cornerRadius(10)
.disabled(isLoading)
.padding(.top, 20)
Spacer()
}
.padding(.horizontal, 30)
.padding(.top, 50)
.alert("Registration", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
} message: {
Text(message)
}
}
// MARK: - Registration function
private func registerUser() {
// Basic validations
guard !email.isEmpty, !password.isEmpty, !confirmPassword.isEmpty else {
message = "Please fill in all fields"
showAlert = true
return
}
guard password == confirmPassword else {
message = "Passwords do not match"
showAlert = true
return
}
guard password.count >= 8 else {
message = "Password must be at least 8 characters"
showAlert = true
return
}
// Start registration process
isLoading = true
message = "Registering user..."
Task {
do {
let result = try await authService.signUp(email: email, password: password)
await MainActor.run {
isLoading = false
message = result
showAlert = true
// Clear fields on success
email = ""
password = ""
confirmPassword = ""
}
} catch {
await MainActor.run {
isLoading = false
message = error.localizedDescription
showAlert = true
}
}
}
}
}
What did we add?
- Additional states:
isLoading(show loading indicator),message(display messages),showAlert(control when to show the alert) - Service instance:
authService = CognitoAuthService() - Validations in registerUser(): checks empty fields, password match, and minimum length
- Async call: uses
Taskfor the async call, shows a spinner while loading, displays an alert with the result, clears fields on success - Improved UI: disables fields while loading, shows spinner in the button, changes button color when disabled
6.3 Password requirements in AWS Cognito
By default, AWS Cognito requires passwords to meet certain security requirements:
- ✅ Minimum 8 characters
- ✅ At least one number
- ✅ At least one uppercase letter
- ✅ At least one lowercase letter
- ✅ At least one special character (!, @, #, $, %, etc.)
Example of a valid password: 1@wAs1234
If you try to register with a password that doesn’t meet these requirements, Cognito will reject the registration with an «invalid password» error. These requirements are configured in the AWS Cognito User Pool and can be modified in the AWS console, but they come set this way by default for better security.
6.4 Testing the registration
- Run the app in the simulator (Cmd + R)
- Enter your real email (to receive the verification code)
- Enter a password that meets the requirements, for example:
1@wAs1234 - Confirm the password
- Tap «Sign Up»
What should happen: You’ll see a spinner while it processes. If everything works, you’ll see an alert: «User registered. Check your email for the verification code». You’ll receive an email from AWS Cognito with a 6-digit code, and the fields will be cleared automatically.
6.5 Verifying the user in AWS
To confirm the user was registered correctly: go to the AWS Cognito console → open your User Pool → go to the «Users» section in the sidebar. You should see your user listed with:
- Username: Your email
- Account status: UNCONFIRMED (because you haven’t entered the code yet)
- Email verified: No
In the next article, we’ll implement the code confirmation to activate the account.
Conclusion
Congratulations! In this article we accomplished:
- ✅ Built a registration UI in SwiftUI
- ✅ Configured an AWS Cognito User Pool from the console
- ✅ Understood the two ways to integrate Cognito (Hosted UI vs Native SDK)
- ✅ Installed the AWS SDK for Swift using Swift Package Manager
- ✅ Implemented user registration that connects with Cognito
- ✅ Tested the registration and received the confirmation email
What’s next?
In the next article we’ll implement the verification code confirmation, build a screen to enter the code received by email, activate the user’s account in Cognito, and handle error cases (invalid code, expired code, etc.).
For now, you already have a functional app that registers users in AWS Cognito. The user receives their code by email and is ready to confirm it.
Remember: The Pool ID and Client ID values in the code are examples. Make sure to use your own real AWS Cognito credentials.
Final thoughts
In this project, our goal was to create a straightforward user registration implementation on iOS using the native AWS Cognito SDK. From configuring AWS Cognito to integrating it with our SwiftUI app, we followed a step-by-step approach so any developer can complete their first registration and authentication without complications.
We know this is just the beginning. Going forward, we’ll incorporate improvements and explore additional features that AWS offers for mobile development. If you have suggestions about topics you’d like to see in upcoming articles, they’re always welcome.
This same project will serve as the foundation to learn and integrate new features, such as: verification code confirmation, Sign In, password recovery, multi-factor authentication (MFA), social authentication (Google, Facebook, Apple Sign In), authorization with roles and user groups, session and token persistence, and much more.
Stay tuned for upcoming updates! Very soon we’ll be expanding mobile development with AWS and taking this integration to the next level.
My name is José Luján, a developer with 20 years of experience working as a developer, manager, and exploring the world of mobile development, AI, and crypto. This was just the first step in integrating AWS with iOS, but there’s still much more to discover. We’ll keep exploring new tools, optimizing our code, and taking our apps to the next level.
See you in the next article! If you want to stay in touch or have a topic suggestion, you can find me on any social network as @josedlujan. 🚀
Until next time!