Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ updates:
commit-message:
prefix: "deps"

- package-ecosystem: "npm"
directory: "/samples/angular/login-pkce"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
labels:
- "dependencies"
commit-message:
prefix: "deps"

- package-ecosystem: "npm"
directory: "/samples/angular/token-refresh"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
labels:
- "dependencies"
commit-message:
prefix: "deps"

- package-ecosystem: "npm"
directory: "/scripts"
schedule:
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/test-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ name: Test JS Frameworks
on:
push:
paths:
- "samples/react/**"
- "samples/**"
pull_request:
paths:
- "samples/react/**"
- "samples/**"
schedule:
- cron: "0 8 * * 1"
workflow_dispatch:
Expand Down Expand Up @@ -55,5 +55,7 @@ jobs:
run: yarn build
- name: Test
working-directory: ${{ matrix.project }}
run: yarn test
if: ${{ hashFiles(format('{0}/vitest.config.ts', matrix.project)) != '' }}
run: |
if grep -q '"test"' package.json; then
Comment thread
ksroda-sa marked this conversation as resolved.
Outdated
yarn test
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ dist/
**/.yarn/*
!**/.yarn/releases
docs/
environments/environment.ts
!environments/environment.example.ts

12 changes: 12 additions & 0 deletions placeholder-map.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,15 @@ js:
placeholder: "{{SCOPES}}"
- pattern: "import.meta.env.POST_LOGOUT_URI"
placeholder: "{{POST_LOGOUT_URI}}"

ts:
- pattern: "environment.issuerUrl"
placeholder: "{{ISSUER_URL}}"
- pattern: "environment.clientId"
placeholder: "{{CLIENT_ID}}"
- pattern: "environment.redirectUri"
placeholder: "{{REDIRECT_URI}}"
- pattern: "environment.scopes"
placeholder: "{{SCOPES}}"
- pattern: "environment.postLogoutUri"
placeholder: "{{POST_LOGOUT_URI}}"
17 changes: 17 additions & 0 deletions samples/angular/login-pkce/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false

[*.md]
max_line_length = off
trim_trailing_whitespace = false
47 changes: 47 additions & 0 deletions samples/angular/login-pkce/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/mcp.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
__screenshots__/

# System files
.DS_Store
Thumbs.db

# Environment secrets
src/environments/environment.ts
12 changes: 12 additions & 0 deletions samples/angular/login-pkce/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"printWidth": 100,
"singleQuote": false,
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
}
940 changes: 940 additions & 0 deletions samples/angular/login-pkce/.yarn/releases/yarn-4.13.0.cjs

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions samples/angular/login-pkce/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defaultSemverRangePrefix: ""

nodeLinker: node-modules

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

yarnPath: .yarn/releases/yarn-4.13.0.cjs
17 changes: 17 additions & 0 deletions samples/angular/login-pkce/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Angular SPA — User Login (PKCE)

Minimal Angular app demonstrating OIDC login with PKCE using `angular-auth-oidc-client`.

## Setup

1. Copy `src/environments/environment.example.ts` to `src/environments/environment.ts` and fill in your SecureAuth values
2. `yarn install`
3. `yarn start`
4. Open https://localhost:4250 (accept the self-signed certificate warning)

## What this demonstrates

- OIDC configuration with Authorization Code + PKCE flow
- Login and logout redirects via SecureAuth
- Displaying authenticated user information
- Error handling with OAuth error hints
67 changes: 67 additions & 0 deletions samples/angular/login-pkce/angular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "yarn",
"analytics": false
},
"newProjectRoot": "projects",
"projects": {
"login-pkce": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": [],
"styles": ["src/styles.css"]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "login-pkce:build:production"
},
"development": {
"buildTarget": "login-pkce:build:development"
}
},
"defaultConfiguration": "development"
},
"test": {
"builder": "@angular/build:unit-test"
}
}
}
}
}
33 changes: 33 additions & 0 deletions samples/angular/login-pkce/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@ciam-quickstart/angular-login-pkce",
"private": true,
"packageManager": "yarn@4.13.0",
"version": "0.0.1",
"scripts": {
"start": "ng serve --ssl --port 4250",
"build": "ng build",
"test": "ng test",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dependencies": {
"@angular/common": "21.2.9",
"@angular/compiler": "21.2.9",
"@angular/core": "21.2.9",
"@angular/forms": "21.2.9",
"@angular/platform-browser": "21.2.9",
"@angular/router": "21.2.9",
"angular-auth-oidc-client": "21.0.1",
"rxjs": "7.8.2",
"tslib": "2.8.1"
},
"devDependencies": {
"@angular/build": "21.2.7",
"@angular/cli": "21.2.7",
"@angular/compiler-cli": "21.2.9",
"jsdom": "29.0.2",
"prettier": "3.8.3",
"typescript": "6.0.3",
"vitest": "4.1.4"
}
}
24 changes: 24 additions & 0 deletions samples/angular/login-pkce/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @snippet:step1:start
// @description Install and configure the OIDC module
import { ApplicationConfig } from "@angular/core";
import { provideAuth } from "angular-auth-oidc-client";
import { environment } from "../environments/environment";
// @snippet:step1:end

// @snippet:step2:start
// @description Configure the OIDC client with your SecureAuth app settings
export const appConfig: ApplicationConfig = {
providers: [
provideAuth({
config: {
authority: environment.issuerUrl,
clientId: environment.clientId,
redirectUrl: environment.redirectUri,
postLogoutRedirectUri: environment.postLogoutUri,
scope: environment.scopes,
responseType: "code",
},
}),
],
};
// @snippet:step2:end
69 changes: 69 additions & 0 deletions samples/angular/login-pkce/src/app/app.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { TestBed } from "@angular/core/testing";
import { OidcSecurityService, LoginResponse } from "angular-auth-oidc-client";
import { Observable, of } from "rxjs";
import { App } from "./app";

function makeLoginResponse(overrides: Partial<LoginResponse>): LoginResponse {
return {
isAuthenticated: false,
userData: null,
accessToken: "",
idToken: "",
configId: "",
...overrides,
} as LoginResponse;
}

describe("App", () => {
let checkAuthFn: () => Observable<LoginResponse>;

const mockOidcService = {
get checkAuth() {
return checkAuthFn;
},
authorize: vi.fn(),
logoff: vi.fn(() => of(null)),
};

beforeEach(async () => {
checkAuthFn = () => of(makeLoginResponse({ isAuthenticated: false, userData: null }));
await TestBed.configureTestingModule({
imports: [App],
})
.overrideProvider(OidcSecurityService, { useValue: mockOidcService })
.compileComponents();
});

it("should render sign in button when not authenticated", async () => {
checkAuthFn = () => of(makeLoginResponse({ isAuthenticated: false, userData: null }));
const fixture = TestBed.createComponent(App);
await fixture.whenStable();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector("button")?.textContent).toContain("Sign in");
});

it("should display user info when authenticated", async () => {
checkAuthFn = () =>
of(
makeLoginResponse({
isAuthenticated: true,
userData: { given_name: "Jane", family_name: "Doe", email: "jane@example.com" },
}),
);
const fixture = TestBed.createComponent(App);
await fixture.whenStable();
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.textContent).toContain("Jane");
expect(compiled.textContent).toContain("Doe");
});

it("should call authorize on sign in click", async () => {
checkAuthFn = () => of(makeLoginResponse({ isAuthenticated: false, userData: null }));
const fixture = TestBed.createComponent(App);
await fixture.whenStable();
const button = fixture.nativeElement.querySelector("button");
button.click();
expect(mockOidcService.authorize).toHaveBeenCalled();
});
});
Loading
Loading