- Add stunning cyberpunk cover page with animated grid background - Integrate Mermaid diagrams for visual architecture and flow charts - Add flexible-alerts for tips, warnings, and notes throughout tutorials - Enhance index.html with 15+ professional plugins (tabs, progress bar, charts) - Add sequence diagrams for authentication and analytics flows - Improve readability with visual callouts and interactive elements - Add graph visualizations for system architecture - Better UX with keyboard shortcuts, word count, and edit links
22 KiB
Authentication Tutorial
Learn how to add user authentication to your AeThex game with email/password, OAuth, and guest login support.
Tip
Authentication is the foundation for cloud saves, multiplayer, and social features. Start here to unlock all AeThex Cloud capabilities.
What You'll Build
A complete authentication system with:
- Email/password registration and login
- OAuth login (Google, GitHub, Discord)
- Guest accounts
- User profiles
- Session management
Time: 30 minutes
Difficulty: Beginner
Prerequisites: Basic GDScript knowledge
flowchart TD
Start[Game Launch] --> Check{Has Session?}
Check -->|Yes| Welcome[Welcome Back!]
Check -->|No| Login[Show Login Screen]
Login --> Choice{Auth Method?}
Choice -->|Email/Password| EmailAuth[Email Login]
Choice -->|OAuth| OAuthFlow[OAuth Flow]
Choice -->|Guest| GuestAuth[Guest Account]
EmailAuth --> Validate{Valid?}
Validate -->|No| Error1[Show Error]
Error1 --> Login
Validate -->|Yes| Auth[Authenticate]
OAuthFlow --> Provider[Select Provider]
Provider --> Browser[Open Browser]
Browser --> Auth
GuestAuth --> Auth
Auth --> Success{Success?}
Success -->|No| Error2[Show Error]
Error2 --> Login
Success -->|Yes| SaveSession[Save Session]
SaveSession --> Welcome
Welcome --> MainMenu[Main Menu]
style Start fill:#00ffff22,stroke:#00ffff
style Welcome fill:#00ff0022,stroke:#00ff00
style Auth fill:#ff00ff22,stroke:#ff00ff
style MainMenu fill:#00ffff22,stroke:#00ffff
Why Add Authentication?
Authentication enables:
- Cloud saves - Save progress across devices
- Multiplayer - Identify players in matches
- Social features - Friends, leaderboards, chat
- Analytics - Track user behavior
- Monetization - In-app purchases, subscriptions
Warning
Never store passwords in plaintext. AeThex Cloud handles all password hashing and security automatically.
Step 1: Connect to AeThex Cloud
First, ensure cloud services are connected:
Note
Session tokens are stored securely and persist across game sessions. Users stay logged in automatically.
# main.gd
extends Node
func _ready():
# Connect to AeThex Cloud
var result = await AeThexCloud.connect_to_cloud()
if result.success:
print("Connected to AeThex Cloud")
check_existing_session()
else:
print("Failed to connect: ", result.error)
show_error("Could not connect to servers")
func check_existing_session():
if AeThexAuth.is_logged_in():
var user = AeThexAuth.get_current_user()
print("Welcome back, ", user.display_name)
go_to_main_menu()
else:
show_login_screen()
Step 2: Create Login UI
Create a login screen with options for different auth methods:
# login_screen.gd
extends Control
@onready var email_field = $VBox/EmailField
@onready var password_field = $VBox/PasswordField
@onready var login_btn = $VBox/LoginButton
@onready var register_btn = $VBox/RegisterButton
@onready var guest_btn = $VBox/GuestButton
@onready var google_btn = $VBox/OAuthButtons/GoogleButton
@onready var github_btn = $VBox/OAuthButtons/GitHubButton
@onready var discord_btn = $VBox/OAuthButtons/DiscordButton
@onready var status_label = $VBox/StatusLabel
func _ready():
login_btn.pressed.connect(_on_login_pressed)
register_btn.pressed.connect(_on_register_pressed)
guest_btn.pressed.connect(_on_guest_pressed)
google_btn.pressed.connect(_on_oauth_pressed.bind("google"))
github_btn.pressed.connect(_on_oauth_pressed.bind("github"))
discord_btn.pressed.connect(_on_oauth_pressed.bind("discord"))
func _on_login_pressed():
var email = email_field.text
var password = password_field.text
if not validate_email(email):
show_status("Invalid email address", Color.RED)
return
if password.length() < 6:
show_status("Password must be at least 6 characters", Color.RED)
return
show_status("Logging in...", Color.YELLOW)
set_buttons_enabled(false)
var result = await AeThexAuth.login_email(email, password)
set_buttons_enabled(true)
if result.success:
show_status("Login successful!", Color.GREEN)
on_login_success()
else:
show_status("Login failed: " + result.error, Color.RED)
func _on_register_pressed():
var email = email_field.text
var password = password_field.text
if not validate_email(email):
show_status("Invalid email address", Color.RED)
return
if password.length() < 6:
show_status("Password must be at least 6 characters", Color.RED)
return
show_status("Creating account...", Color.YELLOW)
set_buttons_enabled(false)
var result = await AeThexAuth.register_email(email, password)
set_buttons_enabled(true)
if result.success:
show_status("Account created! Logging in...", Color.GREEN)
on_login_success()
else:
show_status("Registration failed: " + result.error, Color.RED)
func _on_guest_pressed():
show_status("Creating guest account...", Color.YELLOW)
set_buttons_enabled(false)
var result = await AeThexAuth.login_as_guest()
set_buttons_enabled(true)
if result.success:
show_status("Logged in as guest!", Color.GREEN)
on_login_success()
else:
show_status("Guest login failed: " + result.error, Color.RED)
func _on_oauth_pressed(provider: String):
show_status("Opening " + provider + " login...", Color.YELLOW)
set_buttons_enabled(false)
var result = await AeThexAuth.login_oauth(provider)
set_buttons_enabled(true)
if result.success:
show_status("Logged in with " + provider + "!", Color.GREEN)
on_login_success()
else:
show_status("OAuth login failed: " + result.error, Color.RED)
func validate_email(email: String) -> bool:
var regex = RegEx.new()
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return regex.search(email) != null
func show_status(message: String, color: Color):
status_label.text = message
status_label.modulate = color
func set_buttons_enabled(enabled: bool):
login_btn.disabled = !enabled
register_btn.disabled = !enabled
guest_btn.disabled = !enabled
google_btn.disabled = !enabled
github_btn.disabled = !enabled
discord_btn.disabled = !enabled
func on_login_success():
# Transition to main menu
await get_tree().create_timer(1.0).timeout
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
Step 3: Create Registration Flow
Separate registration screen with additional fields:
# registration_screen.gd
extends Control
@onready var email_field = $VBox/EmailField
@onready var password_field = $VBox/PasswordField
@onready var confirm_password_field = $VBox/ConfirmPasswordField
@onready var display_name_field = $VBox/DisplayNameField
@onready var terms_checkbox = $VBox/TermsCheckbox
@onready var register_btn = $VBox/RegisterButton
@onready var back_btn = $VBox/BackButton
func _ready():
register_btn.pressed.connect(_on_register_pressed)
back_btn.pressed.connect(_on_back_pressed)
func _on_register_pressed():
# Validation
if not validate_input():
return
var email = email_field.text
var password = password_field.text
var display_name = display_name_field.text
register_btn.disabled = true
# Register with additional profile data
var result = await AeThexAuth.register_email(email, password, {
"display_name": display_name
})
register_btn.disabled = false
if result.success:
# Send verification email (optional)
await AeThexAuth.send_verification_email()
show_success("Account created! Check your email to verify.")
else:
show_error("Registration failed: " + result.error)
func validate_input() -> bool:
# Check email
if not validate_email(email_field.text):
show_error("Invalid email address")
return false
# Check password
if password_field.text.length() < 6:
show_error("Password must be at least 6 characters")
return false
# Check password match
if password_field.text != confirm_password_field.text:
show_error("Passwords do not match")
return false
# Check display name
if display_name_field.text.strip_edges().is_empty():
show_error("Display name is required")
return false
# Check terms
if not terms_checkbox.button_pressed:
show_error("You must accept the terms of service")
return false
return true
func validate_email(email: String) -> bool:
var regex = RegEx.new()
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return regex.search(email) != null
func _on_back_pressed():
get_tree().change_scene_to_file("res://scenes/login_screen.tscn")
Step 4: Handle Authentication State
Create a global authentication manager:
# autoload: auth_manager.gd
extends Node
signal login_changed(is_logged_in: bool)
signal user_profile_updated(profile: Dictionary)
var current_user: Dictionary = {}
func _ready():
# Listen for auth state changes
AeThexAuth.login_state_changed.connect(_on_login_state_changed)
func _on_login_state_changed(is_logged_in: bool):
login_changed.emit(is_logged_in)
if is_logged_in:
current_user = AeThexAuth.get_current_user()
print("User logged in: ", current_user.display_name)
else:
current_user = {}
print("User logged out")
func is_logged_in() -> bool:
return AeThexAuth.is_logged_in()
func get_user_id() -> String:
return current_user.get("user_id", "")
func get_display_name() -> String:
return current_user.get("display_name", "Guest")
func get_email() -> String:
return current_user.get("email", "")
func is_guest() -> bool:
return current_user.get("is_guest", false)
func logout():
await AeThexAuth.logout()
get_tree().change_scene_to_file("res://scenes/login_screen.tscn")
Add to Project Settings:
Project → Project Settings → Autoload
Name: AuthManager
Path: res://scripts/auth_manager.gd
Step 5: User Profiles
Display and edit user profiles:
# profile_screen.gd
extends Control
@onready var avatar_texture = $VBox/Avatar
@onready var display_name_label = $VBox/DisplayName
@onready var email_label = $VBox/Email
@onready var user_id_label = $VBox/UserID
@onready var edit_btn = $VBox/EditButton
@onready var logout_btn = $VBox/LogoutButton
func _ready():
edit_btn.pressed.connect(_on_edit_pressed)
logout_btn.pressed.connect(_on_logout_pressed)
load_profile()
func load_profile():
var user = AeThexAuth.get_current_user()
display_name_label.text = user.get("display_name", "Unknown")
email_label.text = user.get("email", "No email")
user_id_label.text = "ID: " + user.get("user_id", "unknown")
# Load avatar if available
if "avatar_url" in user:
load_avatar(user.avatar_url)
func load_avatar(url: String):
var http = HTTPRequest.new()
add_child(http)
http.request_completed.connect(_on_avatar_loaded)
http.request(url)
func _on_avatar_loaded(result, response_code, headers, body):
if response_code == 200:
var image = Image.new()
var error = image.load_png_from_buffer(body)
if error == OK:
avatar_texture.texture = ImageTexture.create_from_image(image)
func _on_edit_pressed():
get_tree().change_scene_to_file("res://scenes/edit_profile.tscn")
func _on_logout_pressed():
AuthManager.logout()
Step 6: Edit Profile
Allow users to update their profile:
# edit_profile.gd
extends Control
@onready var display_name_field = $VBox/DisplayNameField
@onready var bio_field = $VBox/BioField
@onready var save_btn = $VBox/SaveButton
@onready var cancel_btn = $VBox/CancelButton
var original_profile: Dictionary
func _ready():
save_btn.pressed.connect(_on_save_pressed)
cancel_btn.pressed.connect(_on_cancel_pressed)
load_current_profile()
func load_current_profile():
original_profile = AeThexAuth.get_current_user()
display_name_field.text = original_profile.get("display_name", "")
bio_field.text = original_profile.get("bio", "")
func _on_save_pressed():
var new_profile = {
"display_name": display_name_field.text,
"bio": bio_field.text
}
save_btn.disabled = true
var result = await AeThexAuth.update_profile(new_profile)
save_btn.disabled = false
if result.success:
AuthManager.user_profile_updated.emit(new_profile)
show_success("Profile updated!")
await get_tree().create_timer(1.0).timeout
go_back()
else:
show_error("Failed to update profile: " + result.error)
func _on_cancel_pressed():
go_back()
func go_back():
get_tree().change_scene_to_file("res://scenes/profile_screen.tscn")
Step 7: Password Reset
Implement password reset functionality:
# forgot_password.gd
extends Control
@onready var email_field = $VBox/EmailField
@onready var send_btn = $VBox/SendButton
@onready var back_btn = $VBox/BackButton
@onready var status_label = $VBox/StatusLabel
func _ready():
send_btn.pressed.connect(_on_send_pressed)
back_btn.pressed.connect(_on_back_pressed)
func _on_send_pressed():
var email = email_field.text
if not validate_email(email):
show_status("Invalid email address", Color.RED)
return
send_btn.disabled = true
show_status("Sending reset email...", Color.YELLOW)
var result = await AeThexAuth.send_password_reset_email(email)
send_btn.disabled = false
if result.success:
show_status("Reset email sent! Check your inbox.", Color.GREEN)
else:
show_status("Failed: " + result.error, Color.RED)
func validate_email(email: String) -> bool:
var regex = RegEx.new()
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return regex.search(email) != null
func _on_back_pressed():
get_tree().change_scene_to_file("res://scenes/login_screen.tscn")
Step 8: Session Management
Handle session timeouts and refresh:
# session_manager.gd (autoload)
extends Node
const SESSION_CHECK_INTERVAL = 300 # 5 minutes
const SESSION_TIMEOUT = 3600 # 1 hour
var session_timer: Timer
func _ready():
session_timer = Timer.new()
session_timer.timeout.connect(_check_session)
session_timer.wait_time = SESSION_CHECK_INTERVAL
add_child(session_timer)
if AuthManager.is_logged_in():
session_timer.start()
func _check_session():
if not AeThexAuth.is_logged_in():
session_expired()
return
# Refresh session token
var result = await AeThexAuth.refresh_session()
if not result.success:
session_expired()
func session_expired():
session_timer.stop()
show_session_expired_dialog()
func show_session_expired_dialog():
var dialog = AcceptDialog.new()
dialog.dialog_text = "Your session has expired. Please log in again."
dialog.confirmed.connect(func(): get_tree().change_scene_to_file("res://scenes/login_screen.tscn"))
get_tree().root.add_child(dialog)
dialog.popup_centered()
Step 9: Account Linking
Allow users to link multiple auth providers:
# account_linking.gd
extends Control
@onready var linked_providers = $VBox/LinkedProviders
@onready var available_providers = $VBox/AvailableProviders
func _ready():
load_linked_providers()
load_available_providers()
func load_linked_providers():
var user = AeThexAuth.get_current_user()
var providers = user.get("linked_providers", [])
for provider in providers:
var label = Label.new()
label.text = "✓ " + provider.capitalize() + " (linked)"
label.modulate = Color.GREEN
linked_providers.add_child(label)
var unlink_btn = Button.new()
unlink_btn.text = "Unlink"
unlink_btn.pressed.connect(_on_unlink_provider.bind(provider))
linked_providers.add_child(unlink_btn)
func load_available_providers():
var all_providers = ["google", "github", "discord", "twitter"]
var user = AeThexAuth.get_current_user()
var linked = user.get("linked_providers", [])
for provider in all_providers:
if provider not in linked:
var btn = Button.new()
btn.text = "Link " + provider.capitalize()
btn.pressed.connect(_on_link_provider.bind(provider))
available_providers.add_child(btn)
func _on_link_provider(provider: String):
var result = await AeThexAuth.link_oauth_provider(provider)
if result.success:
show_success("Linked " + provider.capitalize())
reload_ui()
else:
show_error("Failed to link: " + result.error)
func _on_unlink_provider(provider: String):
var result = await AeThexAuth.unlink_provider(provider)
if result.success:
show_success("Unlinked " + provider.capitalize())
reload_ui()
else:
show_error("Failed to unlink: " + result.error)
func reload_ui():
# Clear and reload
for child in linked_providers.get_children():
child.queue_free()
for child in available_providers.get_children():
child.queue_free()
await get_tree().process_frame
load_linked_providers()
load_available_providers()
Step 10: Guest Account Conversion
Allow guests to create permanent accounts:
# guest_conversion.gd
extends Control
@onready var email_field = $VBox/EmailField
@onready var password_field = $VBox/PasswordField
@onready var convert_btn = $VBox/ConvertButton
func _ready():
convert_btn.pressed.connect(_on_convert_pressed)
# Only show if user is guest
if not AuthManager.is_guest():
queue_free()
func _on_convert_pressed():
var email = email_field.text
var password = password_field.text
if not validate_input(email, password):
return
convert_btn.disabled = true
var result = await AeThexAuth.convert_guest_to_account(email, password)
convert_btn.disabled = false
if result.success:
show_success("Account created! Your progress is saved.")
await get_tree().create_timer(2.0).timeout
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
else:
show_error("Conversion failed: " + result.error)
func validate_input(email: String, password: String) -> bool:
if not validate_email(email):
show_error("Invalid email address")
return false
if password.length() < 6:
show_error("Password must be at least 6 characters")
return false
return true
func validate_email(email: String) -> bool:
var regex = RegEx.new()
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return regex.search(email) != null
Security Best Practices
1. Never Store Passwords
# ❌ DON'T
var user_password = "secret123" # Never store passwords!
# ✓ DO
# Let AeThexAuth handle authentication securely
2. Validate Input
func validate_password(password: String) -> bool:
if password.length() < 8:
return false
# Check for numbers
var has_number = false
var has_letter = false
for c in password:
if c.is_valid_int():
has_number = true
if c.to_lower() >= 'a' and c.to_lower() <= 'z':
has_letter = true
return has_number and has_letter
3. Use HTTPS Only
# AeThex automatically uses HTTPS
# Never disable SSL verification in production
4. Handle Tokens Securely
# Auth tokens are automatically managed by AeThexAuth
# Don't try to extract or store them manually
5. Rate Limiting
var login_attempts = 0
var last_attempt_time = 0
func attempt_login():
var now = Time.get_ticks_msec() / 1000.0
if now - last_attempt_time > 60:
login_attempts = 0
if login_attempts >= 5:
show_error("Too many attempts. Try again later.")
return
login_attempts += 1
last_attempt_time = now
# Proceed with login
Testing
Test authentication flows:
# test_auth.gd
extends Node
func _ready():
run_tests()
func run_tests():
print("Testing authentication...")
# Test guest login
await test_guest_login()
await AeThexAuth.logout()
# Test email registration
await test_email_registration()
await AeThexAuth.logout()
print("All auth tests passed!")
func test_guest_login():
var result = await AeThexAuth.login_as_guest()
assert(result.success, "Guest login failed")
assert(AeThexAuth.is_logged_in(), "Not logged in after guest login")
print("✓ Guest login works")
func test_email_registration():
var test_email = "test%d@example.com" % Time.get_ticks_msec()
var test_password = "Test123456"
var result = await AeThexAuth.register_email(test_email, test_password)
assert(result.success, "Registration failed")
assert(AeThexAuth.is_logged_in(), "Not logged in after registration")
print("✓ Email registration works")
Next Steps
- Cloud Saves - Save user data
- Analytics Tutorial - Track user behavior
- API Reference - Complete auth API docs
Summary
You've learned how to:
✅ Implement email/password authentication
✅ Add OAuth login (Google, GitHub, Discord)
✅ Support guest accounts
✅ Manage user profiles
✅ Handle password resets
✅ Link multiple auth providers
✅ Convert guest accounts to permanent accounts
Authentication is the foundation for cloud features, social systems, and user engagement!
Ready to save user data? Check out Cloud Saves! 💾