Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ _Avoid_: Integrity-checked module
A non-production vertical slice that validates the riskiest architectural
boundaries without claiming user-ready product value.
_Avoid_: Pilot release, minimum viable product

**Login mode**:
How one interactive identity's session is established on the machine at hand —
through a browser on this machine, or through a code approved on another
device. It is a property of the machine and the moment, not of the identity's
credentials, so the same identity may be established either way.
_Avoid_: Login type, authentication kind
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ help:
@echo ''
@echo 'Against a real deployment (Asgardeo or a local Identity Server 7.x):'
@echo ' make smoke-login Log in and broker one acquisition. Opens a browser.'
@echo ' make smoke-login-device The same, approved on another device. Opens no browser.'
@echo ' make empirical-asgardeo Run the two one-time experiments and print their verdicts.'
@echo ''
@echo 'Both live targets skip cleanly when no deployment is configured.'
Expand Down Expand Up @@ -127,6 +128,14 @@ smoke-build:
smoke-login:
@$(smoke_env) $(GO) test $(SMOKE_FLAGS) $(SMOKE_PACKAGE) -run TestLoginSmoke

# The same deployment, logged in to without a browser. It reads exactly the
# variables smoke-login reads: nothing in the registration is specific to the
# device grant beyond enabling it on the application, and this target exists
# partly to keep that claim honest. Skips when no deployment is configured.
.PHONY: smoke-login-device
smoke-login-device:
@$(smoke_env) $(GO) test $(SMOKE_FLAGS) $(SMOKE_PACKAGE) -run TestDeviceLoginSmoke

# Answers the two questions the redirect-and-narrowing research left open, and
# prints one verdict line each for recording in that document. Skips when no
# deployment is configured.
Expand Down
11 changes: 8 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,17 @@ that rather than presenting it as equivalent.
#### Interactive login modes

> **What ships today.** This section describes the target architecture. The
> first `wso2 login` slice implements browser Authorization Code with PKCE and
> inline client credentials. The Device Authorization Grant and personal access
> tokens validate as legal configuration and refuse at use with the stable code
> shell implements browser Authorization Code with PKCE, the Device
> Authorization Grant, and inline client credentials. Personal access tokens
> validate as legal configuration and refuse at use with the stable code
> `auth.kind_not_implemented` — accepted so that a document written for them
> stays readable, not executed. See
> [the login first slice](plans/login-first-slice.md).
>
> The device grant is reached through the `oauth-device` **kind**, not yet
> through a login-time flag: `wso2 login --device-code` is not in this release.
> So the mode-not-kind rule below states the target, and today an identity that
> can only be established by device says so in its kind.

Browser Authorization Code with PKCE and the Device Authorization Grant are two
**login modes for the same interactive OIDC identity**, not two stored
Expand Down
14 changes: 11 additions & 3 deletions docs/examples/authentication-contexts.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,21 +435,29 @@ availability is per deployment, not universal:
| Kind | Where it is valid | Today |
| --- | --- | --- |
| `oauth-browser` | supported by every identity backend | implemented |
| `oauth-device` | only where the backend advertises the grant; the broker refuses otherwise | validates, refuses at use with `auth.kind_not_implemented` |
| `oauth-device` | only where the backend advertises the grant; the broker refuses otherwise | implemented |
| `client-credentials` | supported by every identity backend; the preferred CI method | implemented |
| `pat` | only for products that accept product-issued long-lived tokens | validates, refuses at use with `auth.kind_not_implemented` |

The last column is the first `wso2 login` slice, not a property of the kind. A
The last column is what the shell implements, not a property of the kind. A
document naming a deferred kind loads and validates — that is deliberate, so
configuration written ahead of the shell stays readable — and refuses only when
an identity using it is actually selected. Examples below that use those kinds
an identity using it is actually selected. Examples below that use `pat`
therefore describe intended shape, not something to run today.

Browser and device are **login modes for one interactive OIDC identity**, not
two stored kinds. `oauth-device` appears as a kind only where an identity can
*only* be established that way; otherwise the mode is chosen at login with
`--device-code`.

That flag is not in this release. Until it arrives, an identity that could be
established either way declares `oauth-browser` and is established that way, and
`oauth-device` is the kind for an identity where the browser mode is not
available at all — a deployment that cannot register the loopback callback URLs,
or one whose users are only ever on machines with no reachable browser. A
developer who merely *happens* to be on a headless machine today is served by a
second identity, not by this kind; that is the gap `--device-code` closes.

### The adapter tier

A kind is first-class only if the shell can derive short-lived, non-renewable
Expand Down
131 changes: 126 additions & 5 deletions docs/guides/login.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ clicking is for.
opaque token, and it refuses rather than hand over a grant it could not
check. See `auth.narrowing_unavailable` in section 8.

A sixth is optional and needed only for logging in from a machine with no
browser: **the device code grant**. Section 5.1 covers it, and nothing else in
the registration changes.

---

## 2. Register the application in Asgardeo
Expand Down Expand Up @@ -460,11 +464,11 @@ For an Identity Server deployment, also set `"type": "onprem"` and use the
| `defaultContext` | The context used when no `--context` flag and no `WSO2_CONTEXT` is given. Must name a context declared below. |
| `identities[].name` | Lower-case letters, digits and dashes, starting with a letter, up to 64 characters. |
| `identities[].type` | `cloud` or `onprem`. Nothing else is accepted. |
| `auth.kind` | `oauth-browser` for a person at a browser. `client-credentials` for CI — see section 7. `oauth-device` and `pat` are named by the schema but not implemented in this release. |
| `auth.kind` | `oauth-browser` for a person at a browser. `oauth-device` for an identity that can only be established without one — see section 5.1. `client-credentials` for CI — see section 7. `pat` is named by the schema but not implemented in this release. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| `auth.issuer` | The issuer, verbatim from its discovery document. |
| `auth.clientId` | The registered public client. |
| `auth.tenant` | The identity's home organization. |
| `auth.credentialRef` | The name the session is stored under in the OS secure store. **Required** for `oauth-browser`; **not allowed** for `client-credentials`. Same character rules as an identity name. |
| `auth.credentialRef` | The name the session is stored under in the OS secure store. **Required** for `oauth-browser` and `oauth-device`; **not allowed** for `client-credentials`. Same character rules as an identity name. |
| `products.<namespace>` | What this identity may reach for one module. The namespace is the module's own name, and follows the same character rules as an identity name. |
| `products.<namespace>.endpoint` | The product's base URL. **Required** on every product entry, and must be an absolute `http` or `https` URL with a host. |
| `products.<namespace>.audience` | What the issued token's `aud` claim must carry. A module asking for any other audience is refused. Conceptually this is the API resource identifier — but on Asgardeo it must be **the client ID**, because that is the only thing Asgardeo puts in `aud`. See section 2.5. |
Expand Down Expand Up @@ -516,6 +520,92 @@ a missing desktop.

The command waits up to five minutes for you.

## 5.1 Logging in without a browser

If the machine you are typing on has no browser that can reach it — you are over
SSH, or inside a container — the login above cannot finish. It waits for the
identity provider to redirect back to `127.0.0.1` on *this* machine, and your
browser's `127.0.0.1` is somewhere else.

The device authorization grant solves that. Nothing is bound to loopback, and
the approval happens on any other device you like.

**When to use it.** Set `"kind": "oauth-device"` on the identity when that
identity can *only* be established this way — a deployment where the loopback
callback URLs cannot be registered, or one whose users are never at a machine
with a reachable browser. It is a property of the identity, not of where you
happen to be sitting today.

If you are usually at a laptop and occasionally on a build box, that is the case
`wso2 login --device-code` is meant for, and **that flag is not in this
release**. Until it arrives, the way to have both is two identities — one
`oauth-browser`, one `oauth-device` — with different `credentialRef` values, and
a context for each.

**What to register.** Everything from section 2 or 3 applies unchanged, with two
differences:

- Add the **Device Code** grant to the application's allowed grant types.
Asgardeo and Identity Server 7.x both support it; on Asgardeo it appears in
the same **Allowed grant types** list as Code and Refresh Token.
- The four loopback callback URLs are not used by this flow. Leave them
registered anyway if the same application also serves browser logins.

Thunder-backed products cannot use this flow at all — Thunder registers no
device grant handler, so its deployments advertise none and the shell refuses
before printing anything.

**The context document** is the section 4.2 document with one word changed:

```json
"auth": {
"kind": "oauth-device",
"issuer": "https://api.asgardeo.io/t/acme/oauth2/token",
"clientId": "REPLACE_WITH_YOUR_CLIENT_ID",
"tenant": "acme",
"credentialRef": "acme-cloud-device"
}
```

Every other field means exactly what it means for `oauth-browser`, and
`credentialRef` is required in the same way. Give it a different value from your
browser identity's if you keep both, so the two sessions do not share a slot.

**What you see:**

```
$ wso2 login

To log in, visit:

https://api.asgardeo.io/t/acme/authenticationendpoint/device.do

and enter the code:

WDJB-MJHT

Or open this link, which carries the code:

https://api.asgardeo.io/t/acme/authenticationendpoint/device.do?user_code=WDJB-MJHT

Waiting for you to approve this login...
```

Open the first URL on your phone or your laptop, type the code, and sign in. The
terminal finishes on its own. The third line is a shortcut for a device you can
paste a link into; the code is deliberately printed on its own line so it
survives being read aloud.

The shell polls at the rate the deployment asks for and stops when the code
expires — usually after ten to fifteen minutes, and never later than fifteen.
Nothing is opened on this machine.

**One difference from browser login worth knowing.** A browser login always
reports a `Subject`. A device login reports one only if the deployment returned
an identity token from this grant, which not every deployment does; RFC 8628
does not require it. The session is established either way, and every product
command afterwards behaves identically.

---

## 6. What login stored, and where
Expand Down Expand Up @@ -710,6 +800,15 @@ usable. In order of likelihood:
- **The issuer does not advertise `S256`.** Set PKCE to mandatory on the
application, as in section 2.2.

There is a third, on a device login only:

> the identity provider does not advertise the device authorization grant

The deployment does not offer the grant, so there is no point printing a code
nobody could approve. Either enable the **Device Code** grant on the
application (section 5.1), or use an `oauth-browser` context. Thunder-backed
deployments have no device grant at all and cannot be made to.

There is a second, differently worded `auth.discovery_failed`:

> no loopback callback port is available for the browser login
Expand Down Expand Up @@ -832,6 +931,18 @@ curl -s "$(curl -s <issuer>/.well-known/openid-configuration | python3 -c 'impor
A serial printed as, for example, `serial=-3A4F8369` is that defect. It no
longer stops a login.

On a device login (section 5.1), the message says which of four endings it was:

| The message says | What it means | What to do |
| --- | --- | --- |
| "the login was declined at the identity provider" | You, or someone at the approval screen, refused the request. | Run `wso2 login` again and approve it. Check the code on screen matches the one in your terminal. |
| "the approval window closed before this login was approved" | The device code expired before anyone approved it. | Run `wso2 login` again and approve it promptly. |
| "this login was not approved in time" | The same, reached by the shell's own deadline rather than the deployment's answer. | As above. |
| "would not start a device authorization" | The deployment refused the request before any code was issued. | Confirm `clientId`, and that the application is registered for the device grant. |

All four leave you in the same place — no session — which is why they share one
code. Only the sentence differs, because only the sentence can.

### `auth.login_not_required`

You ran `wso2 login` on a context whose identity carries its own credential.
Expand All @@ -840,12 +951,14 @@ There is no session to establish; just run the command (section 7).
### `auth.non_interactive`

`wso2 login` was run with `--non-interactive`, or with `WSO2_NON_INTERACTIVE`
set. This is the guard that stops a CI job from waiting on a browser forever.
set. This is the guard that stops a CI job from waiting on a browser forever —
or, on a device context, from waiting forever on an approval no one is there to
give. The message names which of the two it refused.

### `auth.kind_not_implemented`

The context's `auth.kind` is `oauth-device` or `pat`. The schema names them; this
release does not implement them. Use `oauth-browser` or `client-credentials`.
The context's `auth.kind` is `pat`. The schema names it; this release does not
implement it. Use `oauth-browser`, `oauth-device`, or `client-credentials`.

### `auth.session_issuer_mismatch`

Expand Down Expand Up @@ -933,9 +1046,17 @@ before it reaches a browser.

```sh
make smoke-login # log in, prove the session persisted, broker one acquisition
make smoke-login-device # the same, approved on another device (section 5.1)
make empirical-asgardeo # answer the two open questions about Asgardeo's behavior
```

`make smoke-login-device` reads the same variables and needs no new ones — the
only thing it wants from the deployment is the device grant enabled on the same
application. It also reports whether that deployment's device grant returned an
identity token, which is a per-deployment fact this repository has not yet
measured on either product; the answer belongs in the research document beside
the other verdicts.

A passing smoke run ends with the acquisition granted:

```
Expand Down
12 changes: 7 additions & 5 deletions docs/product-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,13 @@ contract is frozen.
### 7.2 Authentication and credentials

> **What ships today.** These are requirements on the product, not a description
> of the current build. The first `wso2 login` slice implements browser
> Authorization Code with PKCE and inline client credentials. Device
> authorization and personal access tokens are accepted as legal configuration
> and refuse at use with the stable code `auth.kind_not_implemented`; there is
> no `--device-code` flag yet. See [the login first slice](plans/login-first-slice.md).
> of the current build. The shell implements browser Authorization Code with
> PKCE, the Device Authorization Grant, and inline client credentials. Personal
> access tokens are accepted as legal configuration and refuse at use with the
> stable code `auth.kind_not_implemented`. Device authorization is selected by
> an identity's `oauth-device` kind; there is no `--device-code` flag yet, so
> the requirement below that it be a login-time mode for a browser identity is
> not yet met. See [the login first slice](plans/login-first-slice.md).

- **P0:** The root shell owns authentication sessions and credential storage.
- **P0:** An **identity** is one login session together with every product for
Expand Down
Loading