Skip to content

Commit b63f5e6

Browse files
authored
feat: authentication examples (#400)
* feat: add account auth example wip * feat: add example for auth server cookie * feat: add working example * feat: remove old example * refactor: remove console logs * feat: add local storage starter * feat: start * feat: working local storage example * chore: update readme * chore: update with warning * refactor: remove unused
1 parent 836087d commit b63f5e6

37 files changed

+2704
-0
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Client-Side Local Storage Authentication Example
2+
3+
This example demonstrates how to authenticate a storefront to Elastic Path Commerce Cloud using client-side local storage. This approach provides a simple method for connecting your frontend to Elastic Path's public-facing endpoints without requiring server-side infrastructure for authentication.
4+
5+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Felasticpath%2Fcomposable-frontend%2Ftree%2Fmain%2Fexamples%2Fauthentication-local-storage&env=NEXT_PUBLIC_EPCC_CLIENT_ID,NEXT_PUBLIC_EPCC_ENDPOINT_URL&project-name=ep-auth-local-storage-example)
6+
7+
## ⚠️ Security Warning
8+
9+
**This example uses local storage for token storage, which has significant security implications:**
10+
11+
- **XSS Vulnerability**: Tokens stored in local storage are accessible by any JavaScript running on your page, making them vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker can inject JavaScript into your site, they can steal the tokens.
12+
- **No HttpOnly Flag**: Unlike cookies, local storage cannot use the HttpOnly flag that would prevent JavaScript access to the token.
13+
- **Persistent by Default**: Tokens remain in local storage until explicitly removed or the browser storage is cleared, potentially exposing them for longer than necessary.
14+
- **CSRF Protection Needed**: When using local storage for authentication, you need to implement additional protection against Cross-Site Request Forgery (CSRF) attacks.
15+
16+
**For production applications with sensitive data, consider:**
17+
18+
- Using HTTP-only cookies for token storage
19+
- Implementing server-side authentication flows
20+
- Implementing proper CSRF protection
21+
22+
This example demonstrates the technical implementation but should be adapted with appropriate security measures for production use.
23+
24+
## Overview
25+
26+
This example shows:
27+
28+
- How to authenticate a storefront to Elastic Path using implicit authentication
29+
- How to store authentication tokens in browser local storage
30+
- How to automatically refresh expired tokens
31+
- How to use the authenticated client to fetch product data from the Elastic Path backend
32+
- How SDK interceptors automatically attach tokens from local storage to API requests
33+
34+
## Authentication Flow
35+
36+
This example uses a React context provider (`StorefrontProvider`) to implement the authentication flow:
37+
38+
1. When the application loads, the StorefrontProvider sets up an interceptor to handle authentication
39+
2. For each API request, the interceptor:
40+
- Checks for an existing authentication token in local storage
41+
- If a token exists and is valid, it attaches it to the request
42+
- If no token exists or the token has expired, it:
43+
- Requests a new access token using the Elastic Path SDK's `createAnAccessToken` method with the implicit grant type
44+
- Stores the new token in the browser's local storage
45+
- Attaches the token to the current request
46+
47+
## How the SDK is Used
48+
49+
The example uses the `@epcc-sdk/sdks-shopper` package to:
50+
51+
1. **Create and configure the client**: Setting the base URL for the Elastic Path API
52+
53+
```typescript
54+
client.setConfig({
55+
baseUrl: process.env.NEXT_PUBLIC_EPCC_ENDPOINT_URL!,
56+
})
57+
```
58+
59+
2. **Create authentication tokens**: Using the `createAnAccessToken` function with the implicit grant flow
60+
61+
```typescript
62+
const authResponse = await createAnAccessToken({
63+
body: {
64+
grant_type: "implicit",
65+
client_id: clientId,
66+
},
67+
})
68+
```
69+
70+
3. **Fetch data**: Using the `getByContextAllProducts` function to retrieve product data from the catalog
71+
```typescript
72+
const response = await getByContextAllProducts()
73+
```
74+
75+
### SDK Interceptors
76+
77+
A key part of this implementation is the use of SDK interceptors to seamlessly handle authentication:
78+
79+
```typescript
80+
client.interceptors.request.use(async (request) => {
81+
let credentials = JSON.parse(
82+
localStorage.getItem(CREDENTIALS_COOKIE_KEY) ?? "{}",
83+
) as AccessTokenResponse | undefined
84+
85+
// check if token expired or missing
86+
if (
87+
!credentials?.access_token ||
88+
(credentials.expires && tokenExpired(credentials.expires))
89+
) {
90+
const authResponse = await createAnAccessToken({
91+
body: {
92+
grant_type: "implicit",
93+
client_id: clientId,
94+
},
95+
})
96+
97+
const token = authResponse.data
98+
localStorage.setItem(CREDENTIALS_COOKIE_KEY, JSON.stringify(token))
99+
credentials = token
100+
}
101+
102+
if (credentials?.access_token) {
103+
request.headers.set("Authorization", `Bearer ${credentials.access_token}`)
104+
}
105+
return request
106+
})
107+
```
108+
109+
This interceptor:
110+
111+
- Reads the token from local storage
112+
- Checks if the token is expired or missing
113+
- Automatically obtains a new token when needed
114+
- Attaches the token as a Bearer token in the Authorization header
115+
- Handles this for all API requests made through the SDK client
116+
117+
## Project Structure
118+
119+
- `src/app/auth/StorefrontProvider.tsx`: React provider that handles authentication logic
120+
- `src/app/client-component.tsx`: Client-side component that fetches and displays products
121+
- `src/app/constants.ts`: Constants including the local storage key for credentials
122+
- `src/app/layout.tsx`: Root layout that wraps the application with the StorefrontProvider
123+
124+
## Local Storage Strategy
125+
126+
The authentication token is stored in the browser's local storage:
127+
128+
- Persists between page reloads and browser sessions
129+
- Easily accessible from anywhere in the client-side application
130+
- Automatically refreshed when expired
131+
132+
This approach is simpler than server-side cookies but has different security considerations:
133+
134+
1. Tokens are accessible to any JavaScript running on the page
135+
2. Tokens persist until explicitly removed or local storage is cleared
136+
3. Ideal for fully client-side applications without server components
137+
138+
## Getting Started
139+
140+
### Prerequisites
141+
142+
- An Elastic Path Commerce Cloud account
143+
- A client ID for your storefront application
144+
145+
### Environment Variables
146+
147+
Create a `.env.local` file with the following variables:
148+
149+
```bash
150+
NEXT_PUBLIC_EPCC_CLIENT_ID=your_client_id
151+
NEXT_PUBLIC_EPCC_ENDPOINT_URL=your_endpoint_url # e.g. https://euwest.api.elasticpath.com
152+
```
153+
154+
### Installation
155+
156+
```bash
157+
npm install
158+
# or
159+
yarn
160+
# or
161+
pnpm install
162+
```
163+
164+
### Development
165+
166+
```bash
167+
npm run dev
168+
# or
169+
yarn dev
170+
# or
171+
pnpm dev
172+
```
173+
174+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
175+
176+
## Learn More
177+
178+
For more information about Elastic Path Commerce Cloud:
179+
180+
- [Elastic Path Documentation](https://documentation.elasticpath.com/)
181+
- [Authentication with Elastic Path](https://documentation.elasticpath.com/commerce-cloud/docs/api/basics/authentication/index.html)
182+
- [Elastic Path Composable Frontend SDK](https://github.com/elasticpath/composable-frontend)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { NextConfig } from "next";
2+
3+
const nextConfig: NextConfig = {
4+
/* config options here */
5+
};
6+
7+
export default nextConfig;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "authentication-local-storage",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint"
10+
},
11+
"dependencies": {
12+
"@epcc-sdk/sdks-shopper": "^0.0.27",
13+
"next": "15.3.1",
14+
"react": "^19.0.0",
15+
"react-dom": "^19.0.0"
16+
},
17+
"devDependencies": {
18+
"@tailwindcss/postcss": "^4",
19+
"@types/node": "^20",
20+
"@types/react": "^19",
21+
"@types/react-dom": "^19",
22+
"tailwindcss": "^4",
23+
"typescript": "^5"
24+
}
25+
}

0 commit comments

Comments
 (0)