Browse Source

authentication code flow refactoring

pull/23416/head
erdemcaygor 12 months ago
parent
commit
4b6ca43766
  1. 26
      npm/ng-packs/apps/dev-app/src/app/app.config.server.ts
  2. 36
      npm/ng-packs/apps/dev-app/src/server.ts
  3. 85
      npm/ng-packs/packages/core/src/lib/services/cookie-storage.service.ts
  4. 1
      npm/ng-packs/packages/core/src/lib/services/index.ts
  5. 1
      npm/ng-packs/packages/core/src/lib/tokens/index.ts
  6. 14
      npm/ng-packs/packages/core/src/lib/tokens/ssr-state.token.ts
  7. 81
      npm/ng-packs/packages/oauth/src/lib/services/remember-me.service.ts

26
npm/ng-packs/apps/dev-app/src/app/app.config.server.ts

@ -1,11 +1,29 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import {
mergeApplicationConfig,
ApplicationConfig,
provideAppInitializer,
inject,
PLATFORM_ID,
TransferState
} from '@angular/core';
import { isPlatformServer } from '@angular/common';
import { provideServerRendering, withRoutes } from '@angular/ssr';
import { appConfig } from './app.config';
import { provideAbpOAuth } from '@abp/ng.oauth';
import { withRoutes, provideServerRendering } from '@angular/ssr';
import { appServerRoutes } from './app.routes.server';
import { SSR_FLAG } from '@abp/ng.core';
const serverConfig: ApplicationConfig = {
providers: [provideAbpOAuth(), provideServerRendering(withRoutes(appServerRoutes))],
providers: [
provideAppInitializer(() => {
const platformId = inject(PLATFORM_ID);
const transferState = inject<TransferState>(TransferState);
if (isPlatformServer(platformId)) {
transferState.set(SSR_FLAG, true);
}
}),
provideServerRendering(withRoutes(appServerRoutes)),
],
};
export const config = mergeApplicationConfig(appConfig, serverConfig);

36
npm/ng-packs/apps/dev-app/src/server.ts

@ -8,6 +8,7 @@ import express from 'express';
import cookieParser from 'cookie-parser';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {environment} from './environments/environment';
// ESM import
import * as oidc from 'openid-client';
@ -21,38 +22,27 @@ const browserDistFolder = resolve(serverDistFolder, '../browser');
const app = express();
const angularApp = new AngularNodeAppEngine();
/**
* Example Express Rest API endpoints can be defined here.
* Uncomment and define endpoints as necessary.
*
* Example:
* ```ts
* app.get('/api/**', (req, res) => {
* // Handle API request
* });
* ```
*/
const ISSUER = new URL('https://localhost:44305/'); // OIDC issuer
const CLIENT_ID = 'MyProjectName_App';
const REDIRECT_URI = 'http://localhost:4200'; // IdP'ye aynen kaydet
const REDIRECT_URI = 'http://localhost:4200';
const SCOPE = 'offline_access MyProjectName';
const config = await oidc.discovery(ISSUER, CLIENT_ID, /* client_secret */ undefined);
const baseCookie = { httpOnly: false, sameSite: 'lax' as const, secure: false, path: '/' };
const secureCookie = { httpOnly: false, sameSite: 'lax' as const, secure: environment.production, path: '/' };
const tokenCookie = { ...secureCookie, httpOnly: false, maxAge: 60 * 60 * 24 * 1 }; // 30 days
app.use(cookieParser());
const sessions = new Map<string, { pkce?: string; state?: string; refresh?: string; at?: string }>();
app.get('/login', async (_req, res) => {
app.get('/authorize', async (_req, res) => {
const code_verifier = oidc.randomPKCECodeVerifier();
const code_challenge = await oidc.calculatePKCECodeChallenge(code_verifier);
const state = oidc.randomState();
const state = oidc.randomState();
const sid = crypto.randomUUID();
sessions.set(sid, { pkce: code_verifier, state });
res.cookie('sid', sid, baseCookie);
res.cookie('sid', sid, secureCookie);
const url = oidc.buildAuthorizationUrl(config, {
redirect_uri: REDIRECT_URI,
@ -96,8 +86,18 @@ app.get('/', async (req, res, next) => {
const tokens = await resp.json();
console.log(tokens);
const expiresInSec =
Number(tokens.expires_in ?? tokens.expiresIn ?? 3600);
const skewSec = 60;
const accessExpiresAt = new Date(
Date.now() + Math.max(0, expiresInSec - skewSec) * 1000
);
sessions.set(sid, { ...sess, at: tokens.access_token, refresh: tokens.refresh_token });
res.cookie('access_token', tokens.access_token, baseCookie);
res.cookie('access_token', tokens.access_token, tokenCookie);
res.cookie('refresh_token', tokens.refresh_token, secureCookie);
res.cookie('expires_at', String(accessExpiresAt.getTime()), tokenCookie);
return res.redirect('/');
} catch (e) {
console.error('OIDC error:', e);

85
npm/ng-packs/packages/core/src/lib/services/cookie-storage.service.ts

@ -0,0 +1,85 @@
import { Injectable, PLATFORM_ID, inject } from '@angular/core';
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class AbpCookieStorageService implements Storage {
private platformId = inject(PLATFORM_ID);
private document = inject(DOCUMENT);
private isBrowser = isPlatformBrowser(this.platformId);
get length(): number {
return this.isBrowser ? this.keys().length : 0;
}
clear(): void {
if (!this.isBrowser) return;
this.keys().forEach(k => this.removeItem(k));
}
getItem(key: string): string | null {
if (!this.isBrowser) return null;
const name = key + '=';
const parts = (this.document.cookie || '').split('; ');
for (const p of parts) {
if (p.startsWith(name)) {
return decodeURIComponent(p.slice(name.length));
}
}
return null;
}
key(index: number): string | null {
if (!this.isBrowser) return null;
return this.keys()[index] ?? null;
}
removeItem(key: string): void {
if (!this.isBrowser) return;
this.setCookie(key, '', { 'max-age': -1, path: '/' });
}
setItem(key: string, value: string): void {
if (!this.isBrowser) return;
this.setCookie(key, encodeURIComponent(value), {
path: '/',
sameSite: 'Lax',
secure: true,
// İstersen kalıcı yapmak için maxAge / expires ekleyebilirsin
// 'max-age': 60 * 60, // 1 saat
});
}
setItemWithExpiry(key: string, value: string, seconds: number): void {
if (!this.isBrowser) return;
this.setCookie(key, encodeURIComponent(value), {
path: '/',
sameSite: 'Lax',
secure: true,
'max-age': Math.max(0, Math.floor(seconds)),
});
}
private keys(): string[] {
const raw = (this.document.cookie || '').split('; ').filter(Boolean);
return raw
.map(c => decodeURIComponent(c.split('=')[0]));
}
private setCookie(name: string, value: string, opts: {
path?: string;
domain?: string;
secure?: boolean;
sameSite?: 'Lax' | 'Strict' | 'None';
expires?: Date;
'max-age'?: number;
}) {
let s = `${name}=${value}`;
if (opts.path) s += `; Path=${opts.path}`;
if (opts.domain) s += `; Domain=${opts.domain}`;
if (opts.sameSite) s += `; SameSite=${opts.sameSite}`;
if (opts.secure) s += `; Secure`;
if (opts.expires) s += `; Expires=${opts.expires.toUTCString()}`;
if (typeof opts['max-age'] === 'number') s += `; Max-Age=${opts['max-age']}`;
this.document.cookie = s;
}
}

1
npm/ng-packs/packages/core/src/lib/services/index.ts

@ -26,3 +26,4 @@ export * from './title-strategy.service';
export * from './timezone.service';
export * from './time.service';
export * from './ssr.service';
export * from './cookie-storage.service';

1
npm/ng-packs/packages/core/src/lib/tokens/index.ts

@ -17,3 +17,4 @@ export * from './tenant-not-found-by-name';
export * from './compare-func.token';
export * from './dynamic-layout.token';
export * from './title-strategy-disable-project-name.token';
export * from './ssr-state.token';

14
npm/ng-packs/packages/core/src/lib/tokens/ssr-state.token.ts

@ -0,0 +1,14 @@
import { inject, InjectionToken, makeStateKey, PLATFORM_ID, TransferState } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
export const SSR_FLAG = makeStateKey<boolean>('SSR_FLAG');
export const APP_STARTED_WITH_SSR = new InjectionToken<boolean>('APP_STARTED_WITH_SSR', {
providedIn: 'root',
factory: () => {
const platformId = inject(PLATFORM_ID);
if (!isPlatformBrowser(platformId)) return true;
const ts = inject(TransferState);
return ts.get(SSR_FLAG, false);
},
});

81
npm/ng-packs/packages/oauth/src/lib/services/remember-me.service.ts

@ -1,32 +1,49 @@
import { AbpLocalStorageService } from '@abp/ng.core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class RememberMeService {
readonly #rememberMe = 'remember_me';
protected readonly localStorageService = inject(AbpLocalStorageService);
set(remember: boolean) {
this.localStorageService.setItem(this.#rememberMe, JSON.stringify(remember));
}
remove() {
this.localStorageService.removeItem(this.#rememberMe);
}
get() {
return Boolean(JSON.parse(this.localStorageService.getItem(this.#rememberMe) || 'false'));
}
getFromToken(accessToken: string) {
const tokenBody = accessToken.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
try {
const parsedToken = JSON.parse(atob(tokenBody));
return Boolean(parsedToken[this.#rememberMe]);
} catch {
return false;
}
}
}
import {
AbpCookieStorageService,
AbpLocalStorageService,
APP_STARTED_WITH_SSR,
} from '@abp/ng.core';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class RememberMeService {
readonly #rememberMe = 'remember_me';
protected readonly localStorageService = inject(AbpLocalStorageService);
protected readonly cookieStorageService = inject(AbpCookieStorageService);
private appStartedWithSsr = inject(APP_STARTED_WITH_SSR, { optional: true });
set(remember: boolean) {
if (this.appStartedWithSsr) {
this.cookieStorageService.setItem(this.#rememberMe, JSON.stringify(remember));
return;
}
this.localStorageService.setItem(this.#rememberMe, JSON.stringify(remember));
}
remove() {
if (this.appStartedWithSsr) {
this.cookieStorageService.removeItem(this.#rememberMe);
return;
}
this.localStorageService.removeItem(this.#rememberMe);
}
get() {
if (this.appStartedWithSsr) {
return Boolean(JSON.parse(this.cookieStorageService.getItem(this.#rememberMe) || 'false'));
}
return Boolean(JSON.parse(this.localStorageService.getItem(this.#rememberMe) || 'false'));
}
getFromToken(accessToken: string) {
const tokenBody = accessToken.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
try {
const parsedToken = JSON.parse(atob(tokenBody));
return Boolean(parsedToken[this.#rememberMe]);
} catch {
return false;
}
}
}

Loading…
Cancel
Save