Authentication Flow

Implement the complete user authentication lifecycle.

What you'll build

A full auth integration from scratch: register a new account, verify the phone number, log in with credentials, fetch and update the user profile, recover a forgotten password, and log out — with proper JWT token handling throughout.

Prerequisites

  • A Store UUID — e.g. e87437f2-3e35-4738-af5e-6307e368255c (staging)
  • cURL or any HTTP client

Registration and login do not require a token. Authenticated endpoints (profile, logout) require the JWT returned by login.


Step 1: Register a New User

Create a new account. The response includes a JWT token so the user is immediately logged in after registration.

cURL

curl -X POST https://ecom-api.staging.blaze.me/api/v1/auth/register \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -d '{
    "data": {
      "type": "users",
      "attributes": {
        "email": "jane@example.com",
        "phone_number": "+15551234567",
        "password": "securepassword123",
        "password_confirmation": "securepassword123",
        "first_name": "Jane",
        "last_name": "Smith",
        "date_of_birth": 631152000000,
        "zip_code": "90001",
        "marketing_email_opt_in": true,
        "marketing_sms_opt_in": false
      }
    }
  }'

JavaScript (fetch)

const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const headers = {
  "Content-Type": "application/vnd.api+json",
  "Accept": "application/vnd.api+json",
  "X-Store": STORE_UUID,
};

const registerRes = await fetch(`${BASE_URL}/api/v1/auth/register`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    data: {
      type: "users",
      attributes: {
        email: "jane@example.com",
        phone_number: "+15551234567",
        password: "securepassword123",
        password_confirmation: "securepassword123",
        first_name: "Jane",
        last_name: "Smith",
        date_of_birth: 631152000000,
        zip_code: "90001",
        marketing_email_opt_in: true,
        marketing_sms_opt_in: false,
      },
    },
  }),
});

const { data: newUser } = await registerRes.json();
const token = newUser.attributes.token;
console.log(`Registered as ${newUser.attributes.email}`);
console.log(`JWT: ${token.substring(0, 20)}...`);

Response (201 Created)

{
  "data": {
    "id": "d4b2e6f3-9c5a-4d7b-8e1f-2a4c6d8e0f1a",
    "type": "users",
    "attributes": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "email": "jane@example.com",
      "first_name": "Jane",
      "last_name": "Smith"
    }
  }
}

Error: Email Already Exists

{
  "errors": [
    {
      "code": "email_already_exists",
      "status": "400",
      "detail": "Email is already in use. Please contact Retailer Support.",
      "source": { "pointer": "/data/attributes/email" }
    }
  ]
}

Note: If the store has phone_verification_required enabled, registration will return a phone_number_requires_confirmation error — proceed to Step 2.


Step 2: Phone Verification

If phone verification is required, request a code and then submit it.

2a: Request Verification Code

curl -X POST https://ecom-api.staging.blaze.me/api/v1/auth/verification \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -d '{
    "data": {
      "type": "users",
      "attributes": {
        "phone_number": "+15551234567"
      }
    }
  }'
const verifyRes = await fetch(`${BASE_URL}/api/v1/auth/verification`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    data: {
      type: "users",
      attributes: { phone_number: "+15551234567" },
    },
  }),
});

const { data: notification } = await verifyRes.json();
console.log(notification.attributes.message);
// "Verification code sent to +1***1234567"

Response

{
  "data": {
    "id": "notif-001",
    "type": "notifications",
    "attributes": {
      "message": "Verification code sent to +1***1234567",
      "level": "info"
    }
  }
}

2b: Submit Verification Code

curl -X POST https://ecom-api.staging.blaze.me/api/v1/auth/verification-check \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -d '{
    "data": {
      "type": "users",
      "attributes": {
        "phone_number": "+15551234567",
        "code": "123456",
        "password": "securepassword123",
        "password_confirmation": "securepassword123",
        "first_name": "Jane",
        "last_name": "Smith"
      }
    }
  }'
const checkRes = await fetch(`${BASE_URL}/api/v1/auth/verification-check`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    data: {
      type: "users",
      attributes: {
        phone_number: "+15551234567",
        code: "123456",
        password: "securepassword123",
        password_confirmation: "securepassword123",
        first_name: "Jane",
        last_name: "Smith",
      },
    },
  }),
});

const { data: verified } = await checkRes.json();
const token = verified.attributes.token;
console.log(`Verified! JWT: ${token.substring(0, 20)}...`);

Response

{
  "data": {
    "id": "d4b2e6f3-9c5a-4d7b-8e1f-2a4c6d8e0f1a",
    "type": "users",
    "attributes": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  }
}

Error: Invalid Code

{
  "errors": [
    {
      "code": "invalid_verification_code",
      "status": "400",
      "detail": "Invalid verification code",
      "source": { "pointer": "/data/attributes/code" }
    }
  ]
}

Step 3: Login

Authenticate with email (or phone) and password to get a JWT token.

cURL

curl -X POST https://ecom-api.staging.blaze.me/api/v1/auth/login \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -d '{
    "data": {
      "type": "users",
      "attributes": {
        "email": "jane@example.com",
        "password": "securepassword123"
      }
    }
  }'

JavaScript (fetch)

const loginRes = await fetch(`${BASE_URL}/api/v1/auth/login`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    data: {
      type: "users",
      attributes: {
        email: "jane@example.com",
        password: "securepassword123",
      },
    },
  }),
});

if (!loginRes.ok) {
  const { errors } = await loginRes.json();
  console.error("Login failed:", errors[0].detail);
  // "Wrong Credentials"
} else {
  const { data: user } = await loginRes.json();
  const token = user.attributes.token;

  // Store the token for authenticated requests
  console.log(`Logged in as ${user.attributes.first_name}`);
  console.log(`JWT: ${token.substring(0, 20)}...`);
}

Response (200 OK)

{
  "data": {
    "id": "c3a1f5d2-8b4e-4f6a-9c2d-1a3b5c7d9e0f",
    "type": "users",
    "attributes": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "email": "jane@example.com",
      "first_name": "Jane",
      "last_name": "Smith"
    }
  }
}

Error: Wrong Credentials

{
  "errors": [
    {
      "code": "bad_login",
      "status": "401",
      "detail": "Wrong Credentials",
      "source": { "pointer": "/data/attributes/email" }
    }
  ]
}

Step 4: Get User Profile

Fetch the authenticated user's profile. Requires the JWT token from login.

cURL

TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

curl -X GET https://ecom-api.staging.blaze.me/api/v1/users/me \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -H "Authorization: Bearer $TOKEN"

JavaScript (fetch)

// Create authenticated headers
const authHeaders = {
  ...headers,
  Authorization: `Bearer ${token}`,
};

const profileRes = await fetch(`${BASE_URL}/api/v1/users/me`, {
  headers: authHeaders,
});

const { data: profile } = await profileRes.json();

console.log(`Name: ${profile.attributes.first_name} ${profile.attributes.last_name}`);
console.log(`Email: ${profile.attributes.email}`);
console.log(`Phone: ${profile.attributes.phone_number}`);
console.log(`Zip: ${profile.attributes.zip_code}`);
console.log(`Type: ${profile.attributes.customer_type}`);
console.log(`Confirmed: ${profile.attributes.is_confirmed}`);

Response

{
  "data": {
    "id": "c3a1f5d2-8b4e-4f6a-9c2d-1a3b5c7d9e0f",
    "type": "users",
    "attributes": {
      "email": "jane@example.com",
      "phone_number": "+15551234567",
      "first_name": "Jane",
      "last_name": "Smith",
      "zip_code": "90001",
      "address": null,
      "billing_address": null,
      "date_of_birth": 631152000000,
      "marketing_email_opt_in": true,
      "marketing_sms_opt_in": false,
      "medical_id": null,
      "drivers_license_id": null,
      "customer_type": "recreational",
      "is_confirmed": true,
      "is_active": true,
      "token": null
    }
  }
}

Step 5: Update User Profile

Update profile fields. Only provided fields are changed.

cURL

curl -X PUT https://ecom-api.staging.blaze.me/api/v1/users/me \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "data": {
      "type": "users",
      "attributes": {
        "zip_code": "90210",
        "marketing_email_opt_in": true
      }
    }
  }'

JavaScript (fetch)

const updateRes = await fetch(`${BASE_URL}/api/v1/users/me`, {
  method: "PUT",
  headers: authHeaders,
  body: JSON.stringify({
    data: {
      type: "users",
      attributes: {
        zip_code: "90210",
        marketing_email_opt_in: true,
      },
    },
  }),
});

const { data: updated } = await updateRes.json();
console.log(`Zip updated to: ${updated.attributes.zip_code}`); // "90210"

Response

{
  "data": {
    "id": "c3a1f5d2-8b4e-4f6a-9c2d-1a3b5c7d9e0f",
    "type": "users",
    "attributes": {
      "email": "jane@example.com",
      "first_name": "Jane",
      "last_name": "Smith",
      "zip_code": "90210",
      "marketing_email_opt_in": true,
      "marketing_sms_opt_in": false,
      "is_confirmed": true,
      "is_active": true
    }
  }
}

Partial update: Use PATCH instead of PUT for the same behavior — only the fields you send are updated.


Step 6: Password Recovery

If the user forgets their password, request a reset link via email.

6a: Request Recovery Email

curl -X POST https://ecom-api.staging.blaze.me/api/v1/auth/recover_password \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -d '{
    "data": {
      "type": "users",
      "attributes": {
        "email": "jane@example.com"
      }
    }
  }'
const recoverRes = await fetch(`${BASE_URL}/api/v1/auth/recover_password`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    data: {
      type: "users",
      attributes: { email: "jane@example.com" },
    },
  }),
});

const { data: recovery } = await recoverRes.json();
console.log(recovery.attributes.message);
// "Password recovery instructions have been sent."

Response

{
  "data": {
    "id": "notif-002",
    "type": "notifications",
    "attributes": {
      "message": "Password recovery instructions have been sent.",
      "level": "info"
    }
  }
}

6b: Reset Password with Token

The user receives an email with a reset token. Use it to set a new password.

RESET_TOKEN="abc123def456"

curl -X POST "https://ecom-api.staging.blaze.me/api/v1/auth/reset_password/$RESET_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -d '{
    "data": {
      "type": "users",
      "attributes": {
        "password": "newSecurePassword456",
        "password_confirmation": "newSecurePassword456",
        "email": "jane@example.com"
      }
    }
  }'
const resetToken = "abc123def456"; // from the recovery email

const resetRes = await fetch(
  `${BASE_URL}/api/v1/auth/reset_password/${resetToken}`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      data: {
        type: "users",
        attributes: {
          password: "newSecurePassword456",
          password_confirmation: "newSecurePassword456",
          email: "jane@example.com",
        },
      },
    }),
  }
);

const { data: reset } = await resetRes.json();
const newToken = reset.attributes.token;
console.log(`Password reset! New JWT: ${newToken.substring(0, 20)}...`);

Response

{
  "data": {
    "id": "c3a1f5d2-8b4e-4f6a-9c2d-1a3b5c7d9e0f",
    "type": "users",
    "attributes": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  }
}

Token expired? If the reset token has expired, you'll get a 400 with code invalid_token. The user must request a new recovery email.


Step 7: Logout

Invalidate the current session and JWT token.

cURL

curl -X DELETE https://ecom-api.staging.blaze.me/api/v1/auth/logout \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c" \
  -H "Authorization: Bearer $TOKEN"

JavaScript (fetch)

const logoutRes = await fetch(`${BASE_URL}/api/v1/auth/logout`, {
  method: "DELETE",
  headers: authHeaders,
});

const { data: logoutData } = await logoutRes.json();
console.log(logoutData.message); // "Logged out successfully"

// Clear the stored token
// token = null;

Response

{
  "data": {
    "message": "Logged out successfully"
  }
}

JWT Token Usage Pattern

Here's a reusable auth helper for your storefront:

const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";

class AuthClient {
  constructor(storeUuid) {
    this.baseUrl = BASE_URL;
    this.storeUuid = storeUuid;
    this.token = null;
  }

  get headers() {
    const h = {
      "Content-Type": "application/vnd.api+json",
      "Accept": "application/vnd.api+json",
      "X-Store": this.storeUuid,
    };
    if (this.token) {
      h["Authorization"] = `Bearer ${this.token}`;
    }
    return h;
  }

  async register(attrs) {
    const res = await fetch(`${this.baseUrl}/api/v1/auth/register`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify({ data: { type: "users", attributes: attrs } }),
    });
    const { data } = await res.json();
    this.token = data.attributes.token;
    return data;
  }

  async login(email, password) {
    const res = await fetch(`${this.baseUrl}/api/v1/auth/login`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify({
        data: { type: "users", attributes: { email, password } },
      }),
    });
    if (!res.ok) throw new Error("Login failed");
    const { data } = await res.json();
    this.token = data.attributes.token;
    return data;
  }

  async getProfile() {
    const res = await fetch(`${this.baseUrl}/api/v1/users/me`, {
      headers: this.headers,
    });
    const { data } = await res.json();
    return data;
  }

  async updateProfile(attrs) {
    const res = await fetch(`${this.baseUrl}/api/v1/users/me`, {
      method: "PUT",
      headers: this.headers,
      body: JSON.stringify({ data: { type: "users", attributes: attrs } }),
    });
    const { data } = await res.json();
    return data;
  }

  async logout() {
    await fetch(`${this.baseUrl}/api/v1/auth/logout`, {
      method: "DELETE",
      headers: this.headers,
    });
    this.token = null;
  }

  get isAuthenticated() {
    return this.token !== null;
  }
}

// Usage
const auth = new AuthClient(STORE_UUID);

await auth.login("jane@example.com", "securepassword123");
console.log(`Authenticated: ${auth.isAuthenticated}`); // true

const profile = await auth.getProfile();
console.log(`Hello, ${profile.attributes.first_name}!`);

await auth.updateProfile({ zip_code: "90210" });
await auth.logout();
console.log(`Authenticated: ${auth.isAuthenticated}`); // false

What's Next?