@sapec/tenalanding-sso 0.4.0

Browser half of the TenaLanding hand-off: read the one-time code out of the URL fragment and exchange it for your application's own tokens.
This is a new version of the view package page, introduced in ProGet 5.3. If it's missing any functionality, you can still view the old version.

Metadata

License UNLICENSED
Downloads 2
Published 10/22/2026 11:16:21 AM

Readme

# @sapec/tenalanding-sso The browser half of signing in through the **TenaLanding** portal. A handful of functions, no framework dependency — it works in Angular 21, Angular 22, React, or plain TypeScript. The backend half is the NuGet package `TenaLanding.Integration.AspNetCore`: it runs the OpenID Connect exchange with the portal, then sends the browser to your SPA's callback page with a one-time code in the URL fragment, and raises `POST {prefix}/sso`, which exchanges that code for **your** application's own tokens. --- ## Install ```ini # .npmrc — no credential; reading the feed is anonymous @sapec:registry=https://nuget.sapec.md/npm/sapec-npm/ ``` ```jsonc // package.json "dependencies": { "@sapec/tenalanding-sso": "^0.4.0" } ``` --- ## Use Two places in your SPA. **The sign-in button** on your own login page: ```ts import { landingSignInUrl } from '@sapec/tenalanding-sso'; location.href = landingSignInUrl(apiBase); // https://your-api/api/landing/login ``` A person already signed in to the portal goes straight in; anyone else signs in to the portal first and comes back. The portal's tile opens the same address. **The callback page** (`Landing:CallbackUrl` on the backend, `/sso` by convention): ```ts import { completeLandingSignIn, LandingSsoError } from '@sapec/tenalanding-sso'; try { const auth = await completeLandingSignIn<AuthResponse>(`${apiBase}/api/landing/sso`); // auth is exactly what your own login returns — store it the way you already do } catch (error) { showError(error instanceof LandingSsoError ? error.message : null); } ``` Angular, in full: ```ts @Component({ selector: 'app-landing-sso', templateUrl: './landing-sso.component.html' }) export class LandingSsoComponent implements OnInit { private readonly auth = inject(AuthService); protected readonly failed = signal(false); protected readonly reason = signal<string | null>(null); async ngOnInit(): Promise<void> { try { await this.auth.completePortalSignIn(); // calls completeLandingSignIn and stores the answer } catch (error: unknown) { this.reason.set(error instanceof LandingSsoError ? error.message : null); this.failed.set(true); } } } ``` **Never retry a failed redemption.** The code is single-use and lives 60 seconds; a retry is guaranteed to fail. Show the message and a link to your normal sign-in page. --- ## API ### `completeLandingSignIn<T>(redeemUrl, options?): Promise<T>` Reads the fragment and, in one call: - `#code=…` — wipes it from history, redeems it with `redeemLandingCode` and returns your response; - `#error=…&error_description=…` — wipes it from history and throws `LandingSsoError` carrying the portal's own sentence (for example that the account was switched off in your application) and `reason` = the error code; - neither — throws `LandingSsoError` with `reason` = `no_code` (the page was opened directly). `options.source` replaces `window` in tests; `options.init` is merged into the `fetch` call. ### `landingSignInUrl(apiBase, routePrefix = '/api/landing'): string` `{apiBase}{routePrefix}/login`, tolerant of trailing and leading slashes — the address that starts a sign-in. ### `readLandingCode(source?): string | null` and `redeemLandingCode<T>(redeemUrl, code, init?): Promise<T>` The two building blocks `completeLandingSignIn` is made of, for when you need them separately. `readLandingCode` returns the code and replaces the history entry; `redeemLandingCode` posts `{ "code": "…" }` and returns the parsed body, or throws `LandingSsoError` with the `detail` (or `title`) of the problem your backend returned. ### `LandingSsoError` `message` (the sentence to show), `status` (the HTTP status, `0` when nothing was sent) and `reason` (`redeem_failed`, `no_code`, or the portal's error code such as `access_denied` or `not_configured`).