# 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

```mermaid
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.

```gdscript
# 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:

```gdscript
# 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:

```gdscript
# 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:

```gdscript
# 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:

```gdscript
# 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:

```gdscript
# 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:

```gdscript
# 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:

```gdscript
# 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:

```gdscript
# 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:

```gdscript
# 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**
```gdscript
# ❌ DON'T
var user_password = "secret123"  # Never store passwords!

# ✓ DO
# Let AeThexAuth handle authentication securely
```

### 2. **Validate Input**
```gdscript
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**
```gdscript
# AeThex automatically uses HTTPS
# Never disable SSL verification in production
```

### 4. **Handle Tokens Securely**
```gdscript
# Auth tokens are automatically managed by AeThexAuth
# Don't try to extract or store them manually
```

### 5. **Rate Limiting**
```gdscript
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:

```gdscript
# 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](FIRST_GAME_TUTORIAL.md#cloud-saves)** - Save user data
- **[Analytics Tutorial](ANALYTICS_TUTORIAL.md)** - Track user behavior
- **[API Reference](../API_REFERENCE.md#aethexauth-singleton)** - 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](FIRST_GAME_TUTORIAL.md#cloud-saves)! 💾
