microg/GmsCore · error · RequestHandlingException

NOT_ALLOWED_ERR

NOT_ALLOWED_ERR

Error message

RP ID $rpId is a public suffix

What it means

RequestHandling.checkIsValid validates WebAuthn request parameters. It rejects an rpId that is itself a public suffix (e.g. 'com', 'co.uk') using Guava's InternetDomainName.from(rpId).isPublicSuffix, throwing RequestHandlingException(NOT_ALLOWED_ERR). Permitting a public-suffix RP ID would let a single credential scope span every site under that suffix, which the WebAuthn spec forbids.

Source

Thrown at play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt:176

    }
}

suspend fun RequestOptions.checkIsValid(context: Context, origin: String, packageName: String?) {
    val allApplicableFacetIds = hashSetOf<String>()
    if (origin.startsWith("https://")) {
        allApplicableFacetIds.add(origin)
        val originUri = origin.toUri()
        // The RP ID must be equal to the origin's effective domain, or a registrable domain
        // suffix of the origin's effective domain: For origin https://login.example.com:1337,
        // login.example.com and example.com are valid rpId,
        // but m.login.example.com and com aren't valid
        // => We don't check topDomainOf(originUri.host) against topDomainOf(rpId), because:
        // 1. rpId m.login.example.com would be a valid rpId for https://login.example.com:1337
        // 2. it excludes internal domains as topDomainOf requires a public FQDN
        //
        // Instead, we first check that rpId is valid, then if it matches the origin host
        if (runCatching { InternetDomainName.from(rpId).isPublicSuffix }.getOrDefault(false)) {
            throw RequestHandlingException(NOT_ALLOWED_ERR, "RP ID $rpId is a public suffix")
        }
        if (
            originUri.host != rpId &&
            originUri.host?.endsWith(".$rpId") != true
        ) {
            throw RequestHandlingException(NOT_ALLOWED_ERR, "RP ID $rpId not allowed from origin $origin")
        }
        // FIXME: Standard suggests doing additional checks, but this is already sensible enough
    } else if ((origin.startsWith("android:apk-key-hash:") || origin.startsWith("android:apk-key-hash-sha256:")) && packageName != null) {
        allApplicableFacetIds.addAll(getAllFacetIdCandidates(context, packageName, origin))
        val sha256facetId = allApplicableFacetIds.firstOrNull { it.startsWith("android:apk-key-hash-sha256:") }
            ?: throw RequestHandlingException(NOT_ALLOWED_ERR, "RP ID $rpId not allowed from origin $origin")
        val fp = Base64.decode(sha256facetId.substring(28), HASH_BASE64_FLAGS).toHexString(":")
        if (!isAssetLinked(context, rpId, fp, packageName)) {
            throw RequestHandlingException(NOT_ALLOWED_ERR, "RP ID $rpId not allowed from origin $origin (expected fingerprint $fp)")
        }
    } else {
        throw RequestHandlingException(NOT_SUPPORTED_ERR, "Origin $origin not supported")

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Set rpId to a registrable domain you control (e.g. 'example.com' or 'login.example.com'), never a bare public suffix
  2. Pre-validate with Guava: InternetDomainName.from(rpId).isPublicSuffix / isTopPrivateDomain before issuing the request
  3. If hosting on a public-suffix platform, register a custom domain or use a subdomain you own as the RP ID
  4. Catch RequestHandlingException(NOT_ALLOWED_ERR) and show which rpId was rejected and why

Example fix

// before
val options = PublicKeyCredentialCreationOptions(rpId = "co.uk", ...)

// after
val domain = InternetDomainName.from("co.uk")
require(!domain.isPublicSuffix) { "rpId must be a registrable domain, not a public suffix" }
val options = PublicKeyCredentialCreationOptions(rpId = "auth.example.co.uk", ...)
Defensive patterns

Strategy: validation

Validate before calling

import com.google.common.net.InternetDomainName

fun rpIdIsRegistrable(rpId: String): Boolean = runCatching {
    InternetDomainName.from(rpId).let { !it.isPublicSuffix }
}.getOrDefault(false)

if (!rpIdIsRegistrable(rpId)) failWith(NOT_ALLOWED_ERR, "rpId must not be a public suffix")

Type guard

fun String.isValidRpId(): Boolean = runCatching {
    InternetDomainName.from(this).isTopPrivateDomain || InternetDomainName.from(this).isUnderPublicSuffix
}.getOrDefault(false)

Try / catch

try {
    fidoClient.handle(options)
} catch (e: RequestHandlingException) {
    if (e.code == NOT_ALLOWED_ERR) showRpIdConfigError(e.message)
    else throw e
}

Prevention

When it happens

Trigger: Calling the FIDO register/sign pipeline with an rpId set to a registrable-suffix-only domain such as 'com', 'org', or 'co.uk' in PublicKeyCredentialCreationOptions/RequestOptions (or their browser wrappers) for an https origin.

Common situations: Developers who read the RP ID from a cookie domain set as a public suffix; misconfigured relying parties that store only the TLD+suffix in config; test harnesses using 'localhost'-adjacent or placeholder suffix domains; providers on shared public-suffix platforms (e.g. *.github.io) using the platform suffix instead of their own subdomain.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/33afb87f2d5dcb12. Report an issue: GitHub.