Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# All files require review from the CIAM team
* @SecureAuthCorp/team-eng-ciam-ui-write
28 changes: 28 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/samples/react/login-pkce"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
labels:
- "dependencies"
commit-message:
prefix: "deps"

- package-ecosystem: "npm"
directory: "/scripts"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
labels:
- "dependencies"
commit-message:
prefix: "deps"

Comment thread
ksroda-sa marked this conversation as resolved.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
60 changes: 60 additions & 0 deletions .github/workflows/extract.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Extract Snippets

on:
push:
branches: [main]
workflow_dispatch:
Comment thread
ksroda-sa marked this conversation as resolved.

permissions:
contents: write

Comment thread
ksroda-sa marked this conversation as resolved.
jobs:
extract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"

- name: Enable Corepack
run: corepack enable

- name: Install dependencies
run: yarn install --immutable

- name: Aggregate manifests
working-directory: scripts
run: yarn aggregate

- name: Extract snippets
working-directory: scripts
run: yarn extract

- name: Validate structure
working-directory: scripts
run: yarn validate

- name: Check for changes
id: diff
run: |
git diff --quiet snippets.json snippet-manifest.yaml || echo "changed=true" >> "$GITHUB_OUTPUT"

- name: Commit updated artifacts
if: steps.diff.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add snippets.json snippet-manifest.yaml
git commit -m "chore: update extracted snippets and manifest"
git push

# - name: Notify ciam-core of snippet changes
# if: steps.diff.outputs.changed == 'true'
# run: |
# gh api repos/SecureAuthCorp/ciam-core/dispatches \
# -f event_type=quickstart-snippets-updated \
# -f 'client_payload[sha]=${{ github.sha }}'
# env:
# GH_TOKEN: ${{ secrets.CIAM_CORE_DISPATCH_TOKEN }}
58 changes: 58 additions & 0 deletions .github/workflows/test-js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Test JS Frameworks

on:
push:
paths:
- "samples/react/**"
pull_request:
paths:
- "samples/react/**"
schedule:
- cron: "0 8 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
find-projects:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.find.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: find
run: |
DIRS=$(find samples -name "package.json" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | sort | jq -R -s -c 'split("\n") | map(select(. != ""))')
echo "matrix=$DIRS" >> "$GITHUB_OUTPUT"

test:
needs: find-projects
if: ${{ needs.find-projects.outputs.matrix != '[]' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
project: ${{ fromJson(needs.find-projects.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Enable Corepack
run: corepack enable
- name: Install dependencies
run: yarn install --immutable
- name: Check formatting
working-directory: ${{ matrix.project }}
run: |
if grep -q '"format:check"' package.json; then
yarn format:check
fi
- name: Build
working-directory: ${{ matrix.project }}
run: yarn build
- name: Test
working-directory: ${{ matrix.project }}
run: yarn test
if: ${{ hashFiles(format('{0}/vitest.config.ts', matrix.project)) != '' }}
940 changes: 940 additions & 0 deletions .yarn/releases/yarn-4.13.0.cjs

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
nodeLinker: node-modules

npmRegistryServer: "https://registry.npmjs.org"

yarnPath: .yarn/releases/yarn-4.13.0.cjs
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
# ciam-quickstart-samples
# CIAM Quickstart Samples

Framework-specific sample apps demonstrating SecureAuth integration. Code is extracted from these samples and displayed in the SecureAuth admin dashboard's Quickstart tab.

Repository for framework-specific integration sample apps
## Structure

```
samples/ # One folder per framework
react/ # React sample apps
login-pkce/ # Login with Auth Code + PKCE
scripts/ # Extraction and validation tools
```

## Adding a new sample

1. Create `samples/<framework>/<flow>/` with a minimal working app
2. Add `@snippet:stepN:start/end` tags and `@description` comments in source files
3. Add a `manifest.yaml` in `samples/<framework>/`
4. Run `cd scripts && yarn all` to validate
5. Open a PR — CI will test the app and validate extraction

## Contributing

This repo is maintained by the SecureAuth CIAM team. External contributions are welcome via pull request — all PRs require team review.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"private": true,
"packageManager": "yarn@4.13.0",
"workspaces": [
"samples/react/*",
"scripts"
]
}
29 changes: 29 additions & 0 deletions placeholder-map.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Maps env var patterns to placeholder tokens per language.
# The extraction script uses these to replace real env var references
# with generic {{PLACEHOLDER}} tokens in extracted snippets.

js:
- pattern: "import.meta.env.VITE_ISSUER_URL"
placeholder: "{{ISSUER_URL}}"
- pattern: "import.meta.env.VITE_CLIENT_ID"
placeholder: "{{CLIENT_ID}}"
- pattern: "import.meta.env.VITE_REDIRECT_URI"
placeholder: "{{REDIRECT_URI}}"
- pattern: "import.meta.env.VITE_SCOPES"
placeholder: "{{SCOPES}}"
- pattern: "import.meta.env.VITE_POST_LOGOUT_URI"
placeholder: "{{POST_LOGOUT_URI}}"
- pattern: "process.env.REACT_APP_ISSUER_URL"
placeholder: "{{ISSUER_URL}}"
- pattern: "process.env.REACT_APP_CLIENT_ID"
placeholder: "{{CLIENT_ID}}"
- pattern: "process.env.REACT_APP_REDIRECT_URI"
placeholder: "{{REDIRECT_URI}}"
- pattern: "process.env.SA_ISSUER_URL"
placeholder: "{{ISSUER_URL}}"
- pattern: "process.env.SA_CLIENT_ID"
placeholder: "{{CLIENT_ID}}"
- pattern: "process.env.SA_CLIENT_SECRET"
placeholder: "{{CLIENT_SECRET}}"
- pattern: "process.env.SA_REDIRECT_URI"
placeholder: "{{REDIRECT_URI}}"
4 changes: 4 additions & 0 deletions samples/react/login-pkce/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
VITE_ISSUER_URL=https://your-tenant.us.secureauth.com/your-workspace
VITE_CLIENT_ID=your-client-id
VITE_REDIRECT_URI=http://localhost:3000/callback
VITE_SCOPES=openid profile email
17 changes: 17 additions & 0 deletions samples/react/login-pkce/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# React SPA — Login with PKCE

Minimal React app demonstrating OIDC login using Authorization Code + PKCE via `react-oidc-context`.

## Setup

1. Copy `.env.example` to `.env` and fill in your SecureAuth values
2. `npm install`
3. `npm run dev`
Comment thread
ksroda-sa marked this conversation as resolved.
Outdated
4. Open http://localhost:3000
Comment thread
ksroda-sa marked this conversation as resolved.

## What this demonstrates

- OIDC configuration with PKCE (no client secret)
- Login redirect to SecureAuth
- Displaying the authenticated user's name
- Logout with redirect
Comment thread
ksroda-sa marked this conversation as resolved.
12 changes: 12 additions & 0 deletions samples/react/login-pkce/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SecureAuth React PKCE Login</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
32 changes: 32 additions & 0 deletions samples/react/login-pkce/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@ciam-quickstart/react-login-pkce",
"private": true,
"version": "0.0.1",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest run",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dependencies": {
"oidc-client-ts": "^3.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-oidc-context": "^3.2.0"
},
"devDependencies": {
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
"@vitejs/plugin-react": "^4.5.0",
"happy-dom": "^20.8.9",
"prettier": "^3.8.1",
"typescript": "^5.8.0",
"vite": "^6.3.0",
"vitest": "^4.1.3"
}
}
60 changes: 60 additions & 0 deletions samples/react/login-pkce/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { render, screen } from "@testing-library/react";
import { expect, test, vi } from "vitest";

const mockUseAuth = vi.fn();

vi.mock("react-oidc-context", () => ({
AuthProvider: ({ children }: { children: React.ReactNode }) => (
<>{children}</>
),
Comment thread
ksroda-sa marked this conversation as resolved.
useAuth: () => mockUseAuth(),
}));

import App from "./App";

test("renders sign in button when not authenticated", () => {
mockUseAuth.mockReturnValue({
isLoading: false,
isAuthenticated: false,
error: null,
signinRedirect: vi.fn(),
});
render(<App />);
expect(screen.getByRole("heading")).toHaveTextContent(
"SecureAuth React PKCE Demo",
);
expect(screen.getByRole("button", { name: "Sign in" })).toBeDefined();
});

test("renders loading state", () => {
mockUseAuth.mockReturnValue({
isLoading: true,
isAuthenticated: false,
error: null,
});
render(<App />);
expect(screen.getByText("Loading...")).toBeDefined();
});

test("renders error state", () => {
mockUseAuth.mockReturnValue({
isLoading: false,
isAuthenticated: false,
error: { message: "Something went wrong" },
});
render(<App />);
expect(screen.getByText("Error: Something went wrong")).toBeDefined();
});

test("renders welcome message when authenticated", () => {
mockUseAuth.mockReturnValue({
isLoading: false,
isAuthenticated: true,
error: null,
user: { profile: { name: "Test User" } },
removeUser: vi.fn(),
});
render(<App />);
expect(screen.getByText("Welcome, Test User")).toBeDefined();
expect(screen.getByRole("button", { name: "Sign out" })).toBeDefined();
});
Loading
Loading