How to Get the Logged-in User Email in Shopify Remix App and Other Frameworks

When you are building Shopify apps, especially embedded apps, we often need access to the email address or identity of the logged-in Shopify user. This is different from what you see in your partner dashboard, as it shows only the store’s registered email ID. The logged-in user can be different from the store’s registered email. This is crucial for showing personalized content, storing user-level settings, or tracking specific admin actions inside the app.

In this guide, I am sharing how to get logged-in Shopify user data. It includes their email, name, and whether the account is an owner or collaborator in Shopify Remix apps.

I will also explain how to replicate the same process in other frameworks like Next.js, Laravel, and Node.js by leveraging Shopify’s OAuth online tokens.

Whether you’re building with Shopify’s official Remix framework or a custom backend, this post gives you everything you need to identify the user behind the screen reliably.

Understanding Shopify Token Types

Before diving into implementation, it’s crucial to understand the difference between Shopify’s token types:

  • Offline Tokens: Associated with the store, persist indefinitely, have full API access
  • Online Tokens: Associated with specific users, expire when the user logs out, and inherit the user’s permissions

The Challenge

The main challenge is that authenticate.admin() in Shopify Remix apps, use offline tokens that do not contain user-specific information. To get user details like email, name, and role, we need online tokens.

Solution Overview

We’ll implement a hybrid approach that:

  1. Uses offline tokens for API operations (avoiding permission issues)
  2. Exchanges session tokens for online tokens to get user information
  3. Provides flexible logging, whether you store tokens in a database or not

Getting the Logged-in User Info in Shopify Remix Apps

Approach 1: API-Based User Fetching (No Token Storage)

This approach is ideal when you want to keep your application simple and don’t want to store access tokens in a database. It’s perfect for logging user activity without the complexity of token management.

Why This Approach Works

Instead of trying to modify Shopify’s authentication flow, we create a parallel system that:

  1. Uses the existing offline token system for API operations
  2. Leverages client-side App Bridge to access user-specific session tokens
  3. Exchanges these session tokens for online tokens to get user information (associated_user)
  4. Provides this information to our application for logging purposes

Step 1: Understanding the Token Exchange Process

When a user opens your app in the Shopify admin, Shopify’s App Bridge provides a session token that contains encrypted user information. We can exchange this session token for an online access token that includes detailed user data.

This exchange happens through Shopify’s OAuth endpoint and requires both your public API key and private API secret. Keeping security in mind, the exchange must happen server-side to keep your API secret secure.

Step 2: Create Client-Side Utility

Our API route serves as the secure bridge between the client-side session token and the user information we need:

// app/routes/api.user-info.jsx
export async function action({ request }) {
  // Extract session token from Authorization header
  const sessionToken = authHeader.replace("Bearer ", "");

  // Decode JWT to get shop domain
  const payload = JSON.parse(/* decode session token */);
  const shop = payload.dest?.replace(/^https:\/\//, "");

  // Exchange for online token (server-side only)
  const exchangeRes = await fetch(`https://${shop}/admin/oauth/access_token`, {
    body: JSON.stringify({
      client_secret: process.env.SHOPIFY_API_SECRET, // Secure server-side
      // ... exchange parameters
    }),
  });

  const userData = await exchangeRes.json();
  return json({ shop, ...userData.associated_user });
}

Why This Step Matters: This API route is crucial because it keeps your sensitive API secret on the server.

Step 3: Building the Client-Side Utility

The client-side utility handles the App Bridge integration and provides a clean interface for getting user information (associated_user):

// app/utils/shopifyUser.js
export async function getLoggedUser(apiKey) {
  const host = new URLSearchParams(window.location.search).get("host");
  
  // Create App Bridge instance
  const app = createApp({ apiKey, host, forceRedirect: true });
  
  // Get session token from Shopify
  const token = await getSessionToken(app);

  // Call our secure API
  const res = await fetch("/api/user-info", {
    method: "POST",
    headers: { Authorization: `Bearer ${token}` },
  });

  return await res.json();
}

Connection to Previous Step: This utility uses the API route we created in Step 2, providing a clean abstraction that any component can use to get user information without worrying about the underlying token exchange complexity.

Step 4: Implementing Application-Level User Context

Rather than fetching user information on every page (which is inefficient), we implement a React context that fetches user data once when the app loads and makes it available throughout the application:

// app/contexts/UserContext.jsx
export function UserProvider({ children, apiKey }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    if (apiKey) {
      getLoggedUser(apiKey)
        .then(userData => {
          setUser(userData);
          // Save to server session for loader access
          saveUserToServerSession(userData);
        });
    }
  }, [apiKey]);

  return (
    <UserContext.Provider value={{ user }}>
      {children}
    </UserContext.Provider>
  );
}

Step 5: Bridging Client and Server for Loader Access

The final piece of our puzzle is making user information available to Remix loaders (server side) or on the client side.

// app/utils/userSession.server.js
export async function saveUserToSession(request, userData) {
  const session = await getSession(request.headers.get("Cookie"));
  session.set("userData", userData);
  return session;
}

export async function getUserFromSession(request) {
  const session = await getSession(request.headers.get("Cookie"));
  return session.get("userData");
}

The Connection: This step completes the bridge between our client-side user fetching (Steps 2-4) and server-side logging needs. Now, any loader can access user information for logging purposes.

Step 6: Implementing User Logging in Route Loaders

With all the infrastructure in place, logging users in individual route loaders becomes straightforward:

// app/routes/dashboard.jsx
export async function loader({ request }) {
  const { admin, session } = await authenticate.admin(request);
  
  // Get user info from session (set by our context)
  const userData = await getUserFromSession(request);
  
  if (userData) {
    console.log("++++ USER ACCESS LOG ++++");
    console.log(`Email: ${userData.email}`);
    console.log(`Role: ${getUserRole(userData)}`);
    console.log(`Shop: ${userData.shop}`);
    console.log(`Timestamp: ${new Date().toISOString()}`);
    
    // Optional: Save to database
    await logUserActivity(userData);
  }
  
  return json({ /* your loader data */ });
}

function getUserRole(userData) {
  if (userData.account_owner) return "Store Owner";
  if (userData.collaborator) return "Agency/Collaborator";
  return "Staff Member";
}

Approach 2: Database Token Storage

For applications that store access tokens in a database, you can implement a more direct server-side approach. This method is beneficial when you need persistent user data or want to perform user-specific operations server-side.

When to Use Database Storage

Consider this approach if you:

  • Need to track user activity over time.
  • Want to perform user-specific operations server-side.
  • Need to cache user permissions for performance.
  • Are building features that require historical user data.

Database Schema Design

// Example database structure
model UserSession {
  id          String @id @default(cuid())
  shopDomain  String
  userEmail   String
  userName    String
  isOwner     Boolean
  onlineToken String
  tokenExpiry DateTime
  lastAccess  DateTime
  createdAt   DateTime @default(now())
}

Server-Side User Resolution

// app/utils/userAuth.server.js
export async function resolveCurrentUser(request, shopDomain) {
  // Extract session token from request
  const sessionToken = extractSessionToken(request);
  
  // Exchange for online token with user info
  const onlineTokenData = await exchangeSessionToken(sessionToken);
  
  // Store/update in database
  const userSession = await upsertUserSession({
    shopDomain,
    userEmail: onlineTokenData.associated_user.email,
    // ... other user data
  });
  
  return userSession;
}

Integration Point: This approach replaces Steps 2-5 from the API-based method with direct database operations, but the logging implementation in Step 6 remains similar.

Comparing Efficiency and Resource Usage

API-Based Approach (Recommended for Most Cases)

Resource Usage: Low to Medium

  • Memory: Minimal – only stores user data in session cookies
  • Database: None required for basic user logging
  • Network: One additional API call per app session
  • Complexity: Medium – requires client-server coordination

When to Choose: Perfect for applications that need simple user identification without complex user management features.

Database Storage Approach

Resource Usage: Medium to High

  • Memory: Higher memory usage as it maintains persistent user sessions.
  • Database: Additional tables and queries required.
  • Network: Similar to the API approach, but with database overhead.
  • Complexity: High – requires database design and token management.

When to Choose: Better for applications that need historical user data, complex user permissions, or server-side user operations.

Common Pitfalls and Solutions

Pitfall 1: Permission Scope Issues

When using online tokens ( useOnlineToken = true ), collaborators may have limited API access compared to store owners. This can cause access scope related failures unexpectedly.

Solution: Use offline tokens for API operations and online tokens only for user identification, as shown in our hybrid approach.

Pitfall 2: Token Expiration

Online tokens expire when users log out, which can break functionality if not handled properly.

Solution: Implement proper error handling and token refresh logic, or use the session-based approach to maintain user information between token refreshes.

Pitfall 3: Security Exposure

It’s easy to accidentally expose sensitive information like API secrets to the client side.

Solution: Always perform token exchanges server-side and only expose public API keys to the client. Use environment variables and never hardcode secrets.

Implementation Checklist

Before implementing user logging in your Shopify app, ensure you have:

  • Understanding of your app’s user identification requirements
  • Proper environment variable setup for API keys and secrets
  • Decision on whether to use database storage or a session-based approach
  • Error handling strategy for token exchange failures
  • Plan for handling different user permission levels
  • Logging infrastructure (console, database, external service)

Testing Your Implementation

To verify your user logging is working correctly:

  1. Test with Store Owner: Log in as the store owner and verify that full user information is captured
  2. Test with Staff: Create a staff account with limited permissions and test access
  3. Test with Collaborator: Add an external collaborator and verify their information is logged correctly
  4. Test Edge Cases: Handle scenarios where token exchange fails or user information is incomplete

Security Considerations

API Key vs API Secret Management

Understanding the difference between Shopify’s API key and API secret is crucial:

  • API Key: This is your app’s public identifier, similar to a client ID in OAuth. It’s safe to expose this to the browser and is required for App Bridge functionality.
  • API Secret: This is your app’s private key and must never be exposed to the client. It’s used exclusively for server-to-server communication with Shopify.

Session Security

When implementing session-based user storage, ensure:

  • Use secure session storage with proper encryption
  • Set appropriate session expiration times
  • Implement session cleanup for logged-out users
  • Use secure cookies in production environments

Real-World Applications

This user logging infrastructure enables several practical applications:

Customer Support: When users contact support, you can quickly identify their role and permissions to provide more targeted assistance.
Analytics and Usage Tracking: Understanding whether your app is primarily used by store owners or delegated to agencies helps inform product development decisions.
Feature Access Control: Different user types might need access to different features based on their role and permissions.
Audit Trails: For compliance or debugging purposes, having detailed logs of who accessed what and when can be invaluable.

Conclusion

How you want to log users logging in to Shopify apps depends on how you want to use that information. The API-based approach we’ve outlined provides a clean, efficient solution that works well for most applications without the complexity of database token management.

You can gain valuable insights into how your app is being used, provide better customer support, and build features that cater to the diverse needs of the Shopify ecosystem.