Friday, November 22, 2024

Understanding SAML: A Technical Guide for Developers

Introduction


Security Assertion Markup Language (SAML) is a standard for exchanging authentication and authorization data between parties. It enables Single Sign-On (SSO), simplifying access to multiple services with a single set of credentials. For developers new to SAML, it can seem complex, but understanding its components and workflows can demystify the process and empower you to implement secure authentication systems.


This post will provide an overview of SAML, followed by a technical deep dive into its key components and how they interact.


What is SAML?


SAML is an XML-based framework for exchanging user authentication and authorization data between two main entities:

Service Provider (SP): The application or service a user wants to access.

Identity Provider (IdP): The entity responsible for authenticating the user and providing identity information to the SP.


The goal of SAML is to allow the SP to rely on the IdP to authenticate users, removing the need for the SP to handle authentication directly.


Key Concepts in SAML


1. SingleSignOnService (SSO)


The SingleSignOnService is a core feature of SAML. It allows users to authenticate once with the IdP and gain access to multiple SPs without re-entering credentials. This is achieved using SAML assertions, which are secure XML documents containing authentication and authorization data.


Workflow:

1. A user attempts to access a protected resource at the SP.

2. The SP generates a SAML Authentication Request and redirects the user to the IdP.

3. The IdP authenticates the user (e.g., via username/password or multi-factor authentication).

4. The IdP generates a SAML Assertion, signs it, and redirects the user back to the SP.

5. The SP validates the assertion and grants the user access.





2. SingleLogoutService (SLO)


The SingleLogoutService ensures that when a user logs out from one SP, they are also logged out from all other connected SPs and the IdP.


Workflow:

1. A user initiates logout at an SP.

2. The SP sends a SAML Logout Request to the IdP.

3. The IdP propagates the logout to other SPs where the user has active sessions.

4. The IdP sends a SAML Logout Response to the initiating SP to confirm the logout.


SLO is critical for maintaining session consistency and preventing orphaned sessions across services.







3. Service Provider (SP)


The SP is the consumer of SAML assertions. It relies on the IdP to authenticate users. SPs trust the IdP’s assertions because they are digitally signed, ensuring integrity and authenticity.


Key Responsibilities:

Generate SAML Authentication Requests.

Validate SAML Assertions received from the IdP.

Enforce authorization rules based on attributes in the assertion.


4. Identity Provider (IdP)


The IdP is the authority that authenticates users and issues SAML assertions. It is the cornerstone of trust in a SAML setup.


Key Responsibilities:

Authenticate users securely.

Provide identity information and attributes to SPs.

Manage the trust relationship with SPs, usually established via certificates.


5. SAML Federation


SAML federation refers to the establishment of trust relationships between multiple SPs and IdPs. This allows seamless interoperability in environments where multiple organizations or services share a common user base.


Federation typically involves:

Exchanging metadata files, which include details about endpoints, certificates, and supported bindings.

Agreeing on shared identifiers and attributes (e.g., email, user ID).

Setting up signing and encryption certificates for secure communication.




How Authentication Data is Transferred in SAML


The SAML protocol involves several key steps:

1. Authentication Request (AuthnRequest):

The SP sends an AuthnRequest to the IdP’s SingleSignOnService endpoint.

This request includes metadata like the SP’s unique identifier, requested bindings, and a unique ID.

2. User Authentication:

The IdP authenticates the user via its chosen method (e.g., credentials, biometrics).

Upon success, the IdP generates a SAML assertion.

3. SAML Assertion:

The assertion contains:

Subject: The authenticated user’s identifier (e.g., email or username).

Conditions: Validity period and audience restrictions.

Attributes: Optional user attributes (e.g., roles, groups).

Authentication Statement: Details about the authentication event.

The assertion is signed using the IdP’s private key for integrity and authenticity.

4. Response:

The IdP sends the SAML assertion in a SAML Response back to the SP, often via the user’s browser.

The SP validates the response using the IdP’s public key.

5. Access Grant:

Upon successful validation, the SP grants the user access to the requested resource.


SAML Bindings: Transporting Data


SAML defines several “bindings” for transporting messages between SPs and IdPs:

HTTP Redirect Binding: Typically used for AuthnRequests, where the request is encoded in a URL.

HTTP POST Binding: Used for SAML Responses, where the assertion is embedded in an HTML form.

SOAP Binding: Often used for back-channel communications like SingleLogoutService.


Each binding ensures secure and reliable transmission of data.


Security Considerations


Signing and Encryption: All assertions and responses should be signed, and sensitive data can be encrypted to prevent tampering and eavesdropping.

Replay Attacks: Use unique IDs and timestamps in SAML messages to prevent reuse.

Certificate Management: Regularly update and rotate signing and encryption certificates to maintain trust.


Practical Implementation Tips


1. Leverage Existing Libraries: Use established SAML libraries (e.g., simplesamlphp, OneLogin for Python, Sustainsys.Saml2 for .NET) to handle complex tasks like request generation and assertion validation.

2. Understand Metadata: Exchange and validate metadata files to establish trust between SPs and IdPs.

3. Test Extensively: Use tools like SAML-tracer or browser developer tools to debug SAML message flows.

4. Monitor Logs: Both SPs and IdPs should log SAML events for troubleshooting and auditing.


Conclusion


SAML is a robust framework for implementing SSO and federated identity management. By understanding its components—SP, IdP, assertions, and services like SSO and SLO—you can build secure and user-friendly authentication systems. While the initial learning curve may be steep, the benefits of implementing SAML make it worth the effort.


If you’re ready to get started, dive into the configuration and setup of your IdP and SP, and let SAML simplify authentication for your users.

Friday, November 15, 2024

Creating Custom Zip Files with Git


Managing project distributions often requires creating archives that exclude certain files or directories. Git offers a streamlined approach to this through the .gitattributes file, enabling precise control over the contents of your archives.

Why Use Git for Custom Archives?

Utilizing Git's archiving capabilities provides several advantages:

  • Consistency: Ensures that archives are generated from a specific commit, maintaining version integrity.
  • Automation: Facilitates the creation of scripts for automated deployment processes.
  • Customization: Allows exclusion of unnecessary files, resulting in cleaner and more efficient distributions.

Understanding the .gitattributes Configuration

The .gitattributes file defines how Git handles various files within your repository. Below is an example configuration:

Let's break down this configuration:

  • General Settings:
    • * text=auto eol=lf: Normalizes line endings to LF, ensuring consistency across different operating systems.
    • *.php diff=php: Specifies that PHP files should use PHP-specific diff settings.
  • Export Ignoring: The export-ignore attribute tells Git to exclude specified files or directories from the archive:
    • .editorconfig, .gitattributes, .gitignore: Configuration files not needed in the distribution.
    • /.git, /.idea: Version control and IDE-specific directories.
    • /tests, /util, /vendor: Directories containing tests, utilities, and dependencies that may not be necessary for the end-user.
    • phpunit.xml: Configuration file for PHPUnit, typically not required in the final product.

Creating the Archive

With the .gitattributes file configured, you can create a zip archive using the following command:

git archive --worktree-attributes --format=zip --output="$(basename "$PWD").zip" HEAD

This command generates a zip file of the current project, excluding the files and directories specified with the export-ignore attribute.

Conclusion

Leveraging Git's archiving features through the .gitattributes file allows for efficient and customized distribution of your projects. By specifying which files to exclude, you can create cleaner archives tailored to your deployment needs.

Tuesday, September 24, 2024

Mammouth AI: One Subscription to Rule Them All?

Introduction

I’ve recently become interested in the world of artificial intelligence. Having worked with several AI tools and purchased subscriptions to ChatGPT and MidJourney, I was intrigued when I started seeing ads for Mammouth AI — a platform that promises access to multiple top-tier AI models for a low monthly fee.

However, with little publicly available information and no free tier options, I was hesitant to dive in without knowing more. But for the sake of my YouTube and blog audience, I decided to take the plunge, sign up, and share my experience. Here’s what I found.

What Is Mammouth AI?

Mammouth AI positions itself as a one-stop platform for accessing various large language models (LLMs) and image generators. The homepage makes some bold claims, offering access to industry leaders like GPT-4, Claude AI, and MidJourney for just €10 a month. The platform promises a user-friendly interface that simplifies the use of these models, even for those who may not be particularly tech-savvy.

Signing Up and Getting Started

The sign-up process was straightforward and required committing to a monthly subscription. I decided to go ahead and choose the Starter plan at €10/month to give you all a firsthand look at what Mammouth AI has to offer.

First Impressions

Upon logging in, I found a clean, straightforward interface that indeed seems designed for ease of use. Mammouth AI offers features like one-click reprompting, where you can easily transfer a prompt from one model to another to see different outputs. 

Exploring Features

  • One/click Reprompting: I tested this by generating a Halloween-themed blog post across several models, including Llama, Claude AI, and Mistral. The process was seamless, and the reprompting feature worked well, providing varied results as expected.
  • Multilingual Capabilities: Since I’m learning Portuguese, I was curious about the platform’s ability to translate content. I had the Mistral model translate the Halloween blog post into European Portuguese, and the result looked good and well-formatted. The translation feature is likely a function of the models themselves rather than a unique offering from Mammouth AI.
  • File Upload and Data Analysis: I attempted to upload a CSV file for analysis, expecting to create a graph. Unfortunately, the platform doesn’t seem to support CSV uploads directly, forcing me to convert the file to a PDF. Even then, none of the models were able to generate the requested graph, with ChatGPT 4o providing Python scripts instead of a visual output. LLMs like ChatGPT can typically read text files natively, but this is restricted through the Mammouth AI interface. I have created graphs in ChatGPT numerous times, so I suspect that this limitation is do to how Mammouth AI in communicating with ChatGPT on the backend.
  • Image and File Upload: I also tested the platform’s ability to analyze images by uploading a photo of our dog. The models offered reasonable guesses about her breed, with detailed descriptions that matched her appearance. This feature seemed more polished than the file upload and analysis tool.
  • Image Generation: I tested the image generation capabilities by asking the models to create a Halloween scene featuring our dog. I used models like Stable Diffusion, DALL-E, and FLUX. While the results varied, based on the variations between the models themselves, each captured different elements of the prompt with varying degrees of success. However, MidJourney’s integration seemed to struggle, and I was unable to successfully generate an image while recording the video. Edit: I was able to generate an image with MidJourney after recording the video.
  • Chat History: The history bar on the left of the interface is welcome, but is very basic with chat naming based on the first few words of the prompt, and delete as the only option. Noted improvements would include renaming capability and a warning before permanently deleting a chat.

Areas for Improvement

While Mammouth AI shows promise, there are a few areas where it could improve:

  • No Free Tier: The absence of a free trial makes it difficult for users to test the platform without committing to a subscription.
  • Limited File Support: The platform’s inability to handle common file types like CSV directly is a significant drawback, especially for users looking to perform data analysis.
  • Unstable Integrations: Some integrations, like MidJourney, were unresponsive during my tests, indicating that the platform might still need some refinement.
  • Interface: History renaming and delete warnings would be easy and helpful improvements to that feature. Native platforms offer a richer experience including image generation controls and special code display boxes that are not available in Mammouth AI.

Final Thoughts

Mammouth AI offers a compelling proposition: access to multiple AI models for a single, affordable subscription. For newbies, AI enthusiasts, and developers who want to experiment with various LLMs without juggling multiple subscriptions, this could be a valuable tool. However, the platform’s limitations mean that it may not yet be the perfect solution for everyone.

I’m still on the fence about committing to Mammouth AI myself, but I plan to continue experimenting with the platform, especially once the promised web search feature becomes available. I’ll be sure to share my findings in a follow-up video or blog post.

Conclusion

Thank you for joining me on this first exploration of Mammouth AI. If you found this review helpful, be sure to check out my YouTube channel, where I’ll continue to explore AI tools, programming tips, and more. If you’d like to support my work, you can visit ThreeLeaf.com and check out the affiliate links on my page.

Thursday, August 29, 2024

Building a Core PHP Single-Page CRUD API

This blog post demonstrates how to create a simple single-page CRUD (Create, Read, Update, Delete) API using only core PHP. The purpose of this project is to provide a clear and straightforward example for beginners who want to understand how to develop a RESTful API in PHP without using frameworks.

System Requirements

To run this project, ensure you have the following installed:

  • PHP: This example uses the latest PHP features and syntax available in version 8.2.
  • MariaDB: The example requires a MariaDB or MySQL instance for the database operations.
  • cURL: A command-line tool for sending HTTP requests. Note that any REST client can be used to test the API endpoints.
  • Composer: The composer.json file is included to provide context for PHP server requirements and dependencies, but it is optional for running this project.

Setup Instructions

  1. Create the api.php file: Place the api.php file on any machine that has PHP configured.
  2. Configure Database Connection: Ensure the database connection settings in the api.php file are correctly set for your database instance. This will allow the API to connect to the database and perform necessary operations.
  3. Run the PHP Server: Start the PHP built-in server in the same folder as api.php to use the example REST calls as-is:
php -S localhost:8080

The Code


<?php

/*
 * CRUD API Example in core PHP.
 *
 * This script provides a basic implementation of a CRUD (Create, Read, Update, Delete) API
 * using PHP, PDO for database interactions, and JSON for data exchange. The API supports
 * creating, reading, updating, and deleting customer records in a MySQL database.
 * It also includes endpoints for setting up and tearing down the customers table, which are
 * available for illustrative purposes and convenience in this example, but should never be 
 * placed in a production environment.
 */

$host = '127.0.0.1';
$dbname = 'testing';
$username = 'username';
$password = 'password';

try {
    /* Establish database connection */
    $pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
    die('Database connection failed: ' . $exception->getMessage());
}

/**
 * Validate user input data for creating or updating a customer.
 *
 * @param string[] $data The data to validate
 *
 * @return string[] The array of validation errors
 */
function validateInput(array $data): array
{
    $errors = [];
    if (empty($data['firstName'])) {
        $errors[] = 'First name is required';
    }
    if (empty($data['lastName'])) {
        $errors[] = 'Last name is required';
    }
    if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Valid email is required';
    }
    return $errors;
}

/* Retrieve HTTP method and path segments */
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($path, '/'));

/* Determine the resource and customer ID (if any) from the URL */
$resource = $segments[1] ?? '';
$customerId = $segments[2] ?? '';

switch ($resource) {
    case 'up':
        if ($method === 'POST') {
            try {
                /* Create 'customers' table if it doesn't exist */
                $sql = 'CREATE TABLE IF NOT EXISTS customers (
                    customerId CHAR(36) DEFAULT uuid() PRIMARY KEY,
                    firstName VARCHAR(255) NOT NULL,
                    lastName VARCHAR(255) NOT NULL,
                    email VARCHAR(255) NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )';
                $pdo->exec($sql);
                echo json_encode(['message' => 'Table created successfully']);
            } catch (PDOException $exception) {
                http_response_code(500);
                echo json_encode(['error' => 'Failed to create table: ' . $exception->getMessage()]);
            }
        } else {
            http_response_code(405);
            echo json_encode(['error' => 'Method not allowed']);
        }
        break;

    case 'down':
        if ($method === 'DELETE') {
            try {
                /* Drop 'customers' table if it exists */
                $sql = 'DROP TABLE IF EXISTS customers';
                $pdo->exec($sql);
                echo json_encode(['message' => 'Table dropped successfully']);
            } catch (PDOException $exception) {
                http_response_code(500);
                echo json_encode(['error' => 'Failed to drop table: ' . $exception->getMessage()]);
            }
        } else {
            http_response_code(405);
            echo json_encode(['error' => 'Method not allowed']);
        }
        break;

    case 'customers':
        switch ($method) {
            case 'GET':
                if ($customerId) {
                    /* Read one customer */
                    $stmt = $pdo->prepare('SELECT * FROM customers WHERE customerId = :customerId');
                    $stmt->execute(['customerId' => $customerId]);
                    $customer = $stmt->fetch(PDO::FETCH_ASSOC);
                    if ($customer) {
                        echo json_encode($customer);
                    } else {
                        http_response_code(404);
                        echo json_encode(['error' => 'Customer not found']);
                    }
                } else {
                    /* Read all customers */
                    $stmt = $pdo->query('SELECT * FROM customers');
                    $customers = $stmt->fetchAll(PDO::FETCH_ASSOC);
                    echo json_encode($customers);
                }
                break;

            case 'POST':
                /* Create a new customer */
                $data = json_decode(file_get_contents('php://input'), true);
                $errors = validateInput($data);
                if (empty($errors)) {
                    $stmt = $pdo->prepare('INSERT INTO customers (firstName, lastName, email) VALUES (:firstName, :lastName, :email)');
                    $stmt->execute([
                        'firstName' => $data['firstName'],
                        'lastName' => $data['lastName'],
                        'email' => $data['email']
                    ]);

                    /* Fetch the customerId of the newly created customer */
                    $stmt = $pdo->query('SELECT customerId FROM customers ORDER BY created_at DESC LIMIT 1');
                    $customerId = $stmt->fetchColumn();

                    echo json_encode(['message' => 'Customer created successfully', 'customerId' => $customerId]);
                } else {
                    http_response_code(400);
                    echo json_encode(['errors' => $errors]);
                }
                break;

            case 'PUT':
                if ($customerId) {
                    /* Update an existing customer */
                    $stmt = $pdo->prepare('SELECT COUNT(*) FROM customers WHERE customerId = :customerId');
                    $stmt->execute(['customerId' => $customerId]);
                    $customerExists = $stmt->fetchColumn();

                    if ($customerExists) {
                        $data = json_decode(file_get_contents('php://input'), true);
                        $errors = validateInput($data);
                        if (empty($errors)) {
                            $stmt = $pdo->prepare('UPDATE customers SET firstName = :firstName, lastName = :lastName, email = :email WHERE customerId = :customerId');
                            $stmt->execute([
                                'firstName' => $data['firstName'],
                                'lastName' => $data['lastName'],
                                'email' => $data['email'],
                                'customerId' => $customerId
                            ]);
                            echo json_encode(['message' => 'Customer updated successfully']);
                        } else {
                            http_response_code(400);
                            echo json_encode(['errors' => $errors]);
                        }
                    } else {
                        http_response_code(404);
                        echo json_encode(['error' => 'Customer not found']);
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(['error' => 'Customer ID is required']);
                }
                break;

            case 'DELETE':
                if ($customerId) {
                    /* Delete an existing customer */
                    $stmt = $pdo->prepare('DELETE FROM customers WHERE customerId = :customerId');
                    $stmt->execute(['customerId' => $customerId]);
                    echo json_encode(['message' => 'Customer deleted successfully']);
                } else {
                    http_response_code(400);
                    echo json_encode(['error' => 'Customer ID is required']);
                }
                break;

            default:
                http_response_code(405);
                echo json_encode(['error' => 'Method not allowed']);
                break;
        }
        break;

    default:
        http_response_code(404);
        echo json_encode(['error' => 'Endpoint not found']);
        break;
}

Operation Sequence Diagram

Below is a sequence diagram that shows the flow of API requests and interactions with the database.

UserUserPHP API ServerPHP API ServerMariaDB DatabaseMariaDB DatabaseBring Application UpPOST /api.php/upCreate `customers` tableTable createdstatusCreate New Customerloop[Create Multiple Customers]POST /api.php/customersInsert new customer recordCustomer createdcustomerIdUpdate Existing CustomerPUT /api.php/customers/{customerId}Update customer record by IDCustomer updatedstatusRetrieve Customer by IDGET /api.php/customers/{customerId}Select customer record by IDCustomer record detailscustomer dataRetrieve All CustomersGET /api.php/customers/Select all customer recordsList of customer recordscustomer database arrayDelete Customer by IDDELETE /api.php/customers/{customerId}Delete customer record by IDCustomer deletedstatusBring Application DownDELETE /api.php/downDrop `customers` tableTable droppedstatus

API Endpoints

This API provides the following endpoints:

1. Bring the Application Up

  • URL: /api.php/up
  • Method: POST
  • Description: Initializes the application and creates the customers table in the database.
curl --request POST \
  --url http://localhost:8080/api.php/up \
  --header 'Content-Type: application/json'

Example Output:

{
  "message": "Table created successfully"
}

2. Create a New Customer

  • URL: /api.php/customers
  • Method: POST
  • Description: Creates a new customer record.
curl --request POST \
  --url http://localhost:8080/api.php/customers \
  --header 'Content-Type: application/json' \
  --data '{
  "firstName": "John",
  "lastName": "Doe",
  "email": "john.doe@example.com"
}'

Example Output:

{
  "message": "Customer created successfully",
  "customerId": "2090a31b-64aa-11ef-9cca-0242ac120002"
}

3. Update an Existing Customer

  • URL: /api.php/customers/{customerId}
  • Method: PUT
  • Description: Updates an existing customer record by ID.
curl --request PUT \
  --url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002 \
  --header 'Content-Type: application/json' \
  --data '{
  "firstName": "Janet",
  "lastName": "Smith",
  "email": "jane.smith@example.com"
}'

Example Output:

{
  "message": "Customer updated successfully"
}

4. Retrieve a Customer by ID

  • URL: /api.php/customers/{customerId}
  • Method: GET
  • Description: Retrieves a customer record by ID.
curl --request GET \
  --url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002

Example Output:

{
  "customerId": "2090a31b-64aa-11ef-9cca-0242ac120002",
  "firstName": "Janet",
  "lastName": "Smith",
  "email": "jane.smith@example.com",
  "created_at": "2024-08-27 19:25:32"
}

5. Retrieve All Customers

  • URL: /api.php/customers
  • Method: GET
  • Description: Retrieves all customer records.
curl --request GET \
  --url http://localhost:8080/api.php/customers/

Example Output:

[
  {
    "customerId": "2090a31b-64aa-11ef-9cca-0242ac120002",
    "firstName": "Janet",
    "lastName": "Smith",
    "email": "jane.smith@example.com",
    "created_at": "2024-08-27 19:25:32"
  },
  {
    "customerId": "9b9a1019-65c0-11ef-87f6-0242ac140002",
    "firstName": "John",
    "lastName": "Marsh",
    "email": "john.marsh@example.com",
    "created_at": "2024-08-29 04:38:59"
  }
]

6. Delete a Customer

  • URL: /api.php/customers/{customerId}
  • Method: DELETE
  • Description: Deletes a customer record by ID.
curl --request DELETE \
  --url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002

Example Output:

{
  "message": "Customer deleted successfully"
}

7. Bring the Application Down

  • URL: /api.php/down
  • Method: DELETE
  • Description: Cleans up and destroys the customers table in the database.
curl --request DELETE \
  --url http://localhost:8080/api.php/down \
  --header 'Content-Type: application/json'

Example Output:

{
  "message": "Table dropped successfully"
}

Conclusion

This example demonstrates a fundamental RESTful API using core PHP and MariaDB. It is an excellent starting point for beginners looking to understand the basics of API development without requiring a framework. You can use this code to build upon and expand your knowledge of RESTful services in PHP.