Readme
# @sapec/tenalanding-sso
The browser half of the **TenaLanding** hand-off. Two functions, no framework dependency, no `node_modules`
of its own — it works in Angular 21, Angular 22, React, or plain TypeScript.
The backend half is the NuGet package `TenaLanding.Integration.AspNetCore`, which raises
`POST {prefix}/sso` in your application.
---
## Install
```jsonc
// package.json
"dependencies": {
"@sapec/tenalanding-sso": "file:../../.nuget-local/sapec-tenalanding-sso-0.2.0.tgz"
}
```
---
## Use
The portal redirects the browser to `{yourPortalUrl}{ssoCallbackPath}#code=…`. The code travels in the
**fragment**, so it never reaches a server log or a `Referer` header. Your callback route reads it and hands
it to your own backend, which exchanges it server-to-server and answers with your own tokens.
```ts
import { readLandingCode, redeemLandingCode, LandingSsoError } from '@sapec/tenalanding-sso';
const code = readLandingCode(); // also wipes it from the browser history
if (code) {
const auth = await redeemLandingCode<AuthResponse>('/api/landing/sso', code);
// auth is exactly what your own /auth/login returns — store it the way you already do
}
```
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);
async ngOnInit(): Promise<void> {
const code = readLandingCode();
if (!code) {
this.failed.set(true);
return;
}
try {
await this.auth.signInWithLandingCode(code);
} catch {
this.failed.set(true);
}
}
}
```
**Never retry a failed redemption.** The code is single-use and lives 60 seconds; a retry is guaranteed to
fail and only muddies the audit trail. Show the message and a link to your normal sign-in page.
---
## API
### `readLandingCode(source?): string | null`
Reads `code` out of `location.hash` and immediately replaces the history entry with the same page minus the
fragment, so a refresh or a shared URL cannot carry the credential. Returns `null` when there is no code —
including when the page was opened directly rather than through the portal.
`source` is only for tests; it defaults to `window`.
### `redeemLandingCode<T>(redeemUrl, code, init?): Promise<T>`
`POST`s `{ "code": "…" }` as JSON and returns the parsed body — **your** application's auth response,
unchanged, because the backend package serializes whatever your `ILandingSignIn<T>` returned.
Throws `LandingSsoError` on a non-2xx answer, carrying `status` and the `detail` (or `title`) of the RFC 7807
problem document your application returned, so the message you show the person is the one your backend chose.
`init` is merged into the `fetch` call — use it for `credentials`, an `AbortSignal`, or extra headers.