auth-guest
The auth-guest service gives guest users access to a share without a full
OpenCloud account. When a share is created for a user of type
USER_TYPE_GUEST, the service issues a one-time guest link token; redeeming
that token exchanges it for a signed session cookie that authenticates the
guest.
When the session or the link token is no longer valid, the guest can renew: the service issues a new link token together with a one-time PIN. The guest then either redeems the new link or exchanges the PIN for a fresh session.
It is disabled by default. Set OC_ENABLE_GUEST_LINKS=true to enable the guest
links feature and start the service.
Overview
- Consumes the share lifecycle events
ShareCreated,ShareRemovedandShareExpired. - Publishes the
GuestTokenCreatedevent (initial token) and theGuestTokenRenewedevent (refresh, carrying the new token and the PIN). - Exposes unauthenticated endpoints to redeem a token, renew it and verify a PIN; redeeming a token or verifying a PIN sets a session cookie.
- Stores only hashes of the token secret and of the PIN and deletes the stored record when the share is removed or expires.
Guest links flow
The following sequence diagram describes the guest links flow:
sequenceDiagram
autonumber
actor User as Guest user
participant Web as Web client
participant Redeem as Redeem endpoint
participant Proxy as OpenCloud proxy
participant Graph as Graph / sharedWithMe
participant DAV as WebDAV
participant Reva as Reva
User->>Web: Open guest link with valid token
Web->>+Redeem: Redeem Token
Note right of Redeem: Validate Token
Redeem->>+Reva: Get Share
Reva->>-Redeem: Share
Note right of Redeem: Validate Share, Mark Token used
Redeem->>-Web: Set Cookie, return shareid
Note right of Web: HTTP only cookie with signed JWT (24h by default)
Web->>+Proxy: "/graph/me/drives/sharedWithMe"
Proxy->>+Reva: validate token extracted from JWT
Note right of Reva: Sign Reva Token for Guest User
Reva->>-Proxy: Authenticated
Proxy->>+Graph: "/graph/me/drives/sharedWithMe"
Note right of Proxy: Using Reva Token
Graph->>+Reva: Requests to ShareProvider
Reva->>-Graph: Shares
Graph->>-Proxy: driveItems (all shares for the Guest User)
Proxy->>-Web: driveItems
Note right of Web: Extracts driveItem for the specific share
Web->>+Proxy: PROPFIND (resource id extracted from driveItem)
Note right of Web: Using Cookie
Proxy->>+Reva: validate token extracted from JWT
Note right of Reva: Sign Reva Token for Guest User
Reva->>-Proxy: Authenticated
Proxy->>+DAV: PROPFIND
Note right of Proxy: Using Reva Token
DAV->>+Reva: Requests to StorageProvider
Reva->>-DAV: StorageProvider Responses
DAV->>-Proxy: PROPFIND Response
Proxy->>-Web: PROPFIND Response
Renewal and PIN flow
A guest session (signed JWT) is valid for 24 hours by default
(AUTH_GUEST_JWT_TTL); a link token is single-use and valid for 30 minutes.
Renewal starts from a failed attempt:
- an authenticated request made with an expired session is rejected with
401; the browser still holds the (expired) session cookie; redeemon a link that was already used answers409, and on an expired link401; the client still holds the link token.
renew requires the share id (permissionId) and is authorized by either the
(possibly expired) session cookie or the previous link token, whichever the
client has; the credential must belong to the given share. It does not create a
session. It requires an existing token record for the share, validates the
share, generates a new link token and a 6-digit PIN (both valid for 30 minutes),
overwrites the stored token and PIN hashes (invalidating the previous link and
PIN), resets the redeemed flag so the new link can be redeemed, and publishes
GuestTokenRenewed. It answers 200. This works whether the record is unused,
expired or already redeemed (the guest already redeemed an earlier link); if
publishing fails, the previous record is restored.
To turn the new credentials into a session the guest either redeems the link
(verify/token) or enters the PIN (verify/pin); both return a session
cookie and the permissionId. verify/pin does not consume the link token.
sequenceDiagram
autonumber
actor User as Guest user
participant Web as Web client
participant Renew as Renew endpoint
participant Redeem as Redeem endpoint
participant Pin as Verify PIN endpoint
participant Bus as Event bus
Note over Web: Session expired / previous link used or expired
Web->>+Renew: POST renew { permissionId } (+ session cookie or link token)
Note right of Renew: Validate share, generate token + PIN, store hashes
Renew->>Bus: publish GuestTokenRenewed
Note right of Bus: External consumer delivers token + PIN (out of scope)
Renew->>-Web: 200 OK
alt Guest opens the new link
User->>Web: Open link
Web->>+Redeem: POST redeem { token }
Note right of Redeem: Validate token + share, mark used
Redeem->>-Web: Set Cookie, return permissionId
else Guest enters the PIN
User->>Web: Enter PIN
Web->>+Pin: POST verify/pin { pin, permissionId }
Note right of Pin: Validate share, consume PIN (single-use)
Pin->>-Web: Set Cookie, return permissionId
end
Note over Web: HTTP only cookie, signed JWT (24h default)
Token lifecycle
- Issue — on the consumed
ShareCreatedevent, where the grantee is a guest, the service generates a random secret and stores a record keyed by the hash of the share id. It then publishes theGuestTokenCreatedevent with the token. - Redeem — the guest posts the token to
POST /graph/v1beta1/extensions/org.libregraph/guestLinks/verify/token. The service validates the token and the share, marks the token as used and returns a signed JWT session token in a cookie plus the share'spermissionIdin the response body. Tokens are single-use and valid for 30 minutes; the session cookie lives for 24 hours by default (AUTH_GUEST_JWT_TTL). - Renew — the guest posts the
permissionIdtoPOST /graph/v1beta1/extensions/org.libregraph/guestLinks/renew, authorized by either the previous link token ({ "token": "<token>" }) or the (possibly expired) session cookie, which must both belong to that share. An existing record is required; otherwise the request fails withtokenNotFound. The service validates the share, generates a new link token and a 6-digit PIN (both valid for 30 minutes), overwrites the stored token and PIN hashes (invalidating the previous link and PIN) and resets the redeemed flag so the new link can be redeemed, then publishes theGuestTokenRenewedevent. This works whether the record is unused, expired or already redeemed. If publishing fails, the previous record is restored. - Verify PIN — the guest posts the PIN and the
permissionIdtoPOST /graph/v1beta1/extensions/org.libregraph/guestLinks/verify/pin. The service validates the record and the share, compares the PIN against the stored argon2id hash and, on success, clears the PIN (single-use) and returns a signed JWT session token in a cookie plus the share'spermissionId. Verifying the PIN does not consume the magic link.
Endpoints
All error responses have the body
{ "errorType": "<type>", "message": "<msg>", "permissionId": "<share-id>" }.
POST .../guestLinks/verify/token
- Request body:
{ "token": "<token>" } - Success:
200, session cookie,{ "permissionId": "<share-id>" } - Errors:
400 invalidRequest,401 tokenInvalid,401 tokenExpired,404 tokenNotFound,409 tokenAlreadyRedeemed,404 shareNotFound,410 shareExpired,500 internalError
POST .../guestLinks/renew
- Request body:
{ "permissionId": "<share-id>", "token": "<previous link token>" }(tokenoptional when the session cookie is sent) - Authorization: the share id is required; the body link token or the session cookie must belong to it, the link token takes precedence
- Success:
200(no session cookie; the new link/PIN are delivered out of band) - Errors:
400 invalidRequest,401 tokenInvalid,401 sessionInvalid,404 tokenNotFound,404 shareNotFound,410 shareExpired,409 tokenAlreadyRedeemed,503 serviceUnavailable,500 internalError
POST .../guestLinks/verify/pin
- Request body:
{ "pin": "<pin>", "permissionId": "<share-id>" } - Success:
200, session cookie,{ "permissionId": "<share-id>" } - Errors:
400 invalidRequest,401 pinInvalid,401 pinExpired,404 shareNotFound,410 shareExpired,500 internalError
Configuration
The service is configured via AUTH_GUEST_* environment variables or a
auth-guest.yaml file.
Both transports are enabled by default: the service consumes and publishes events and serves the HTTP endpoints. Either transport can be disabled:
AUTH_GUEST_EVENTS_DISABLED=true— the service does not consume events (no tokens are created or cleaned up) andrenewcannot publish its event, so it responds with503 serviceUnavailable.AUTH_GUEST_HTTP_DISABLED=true— the service does not serve the HTTP endpoints (verify/token,renew,verify/pin).
The token and PIN lifetimes are fixed (30 minutes each) and are not configurable.
Relevant options:
AUTH_GUEST_JWT_COOKIE_NAME,AUTH_GUEST_JWT_TTL— session cookie name and lifetime.- Guest session tokens are signed with a key derived from
OC_JWT_SECRET. There is no separate secret to configure; rotatingOC_JWT_SECRETinvalidates all guest sessions. AUTH_GUEST_TOKENS_STORAGE_ROOT— where guest link token records are stored.AUTH_GUEST_SERVICE_ACCOUNT_ID,AUTH_GUEST_SERVICE_ACCOUNT_SECRET— service account used to query the gateway for share metadata.AUTH_GUEST_NUM_CONSUMERS— number of concurrent event consumers.OC_REVA_GATEWAY— CS3 gateway used to look up shares.