instructure/canvas-lms · error · OAuthValidationError
User is from unacceptable tenant
Error message
User is from unacceptable tenant %{tenant}. What it means
Raised by the Microsoft authentication provider's unique_id when allowed_tenants is configured (non-empty, not 'common', skip_tenant_verification off) and the 'guests' branch is not taken, but the ID token's tid (tenant ID) claim is not in the allowed tenant list. The user authenticated with Microsoft but belongs to a tenant the Canvas admin did not allow.
Solutions
- Compare the tid in the error to the configured tenant list and add the missing tenant GUID.
- Use the directory's tenant GUID (Azure portal > Azure AD > Overview), not the domain name, in the allowed tenants config.
- Set skip_tenant_verification or include 'common' if all Microsoft users should be permitted.
- Ask the user to sign in with an account from an allowed tenant.
Example fix
# before allowed_tenants: "contoso.onmicrosoft.com" # after allowed_tenants: "72f988bf-86f1-41af-91ab-2d7cd011db47"
Defensive patterns
Strategy: validation
Validate before calling
tid = id_token["tid"]
allowed = provider_settings["allowed_tenants"].split(",").map(&:strip)
raise "tenant #{tid} not allowed" unless allowed.empty? || allowed.include?("common") || allowed.include?(tid) Try / catch
begin
user.unique_id
rescue OAuthValidationError => e
Rails.logger.warn("Microsoft tenant rejected: #{e.message}")
render json: { error: "tenant_not_allowed" }, status: :forbidden
end Prevention
- Use directory GUIDs (Azure portal > Overview > Tenant ID), not domain names
- Re-check the allow-list after tenant migrations/mergers
- Log the tid claim on rejections to ease allow-list updates
When it happens
Trigger: unique_id runs during Microsoft OAuth callback with a fixed allow-list of tenants; id_token['tid'] (user's home tenant GUID) is not one of the configured allowed_tenants values, so the elsif fails and raises.
Common situations: User signs in with a personal or different-org Microsoft account; admin allow-list contains tenant domain names instead of tenant GUIDs (tid is a GUID); org migrated to a new tenant ID after rebranding/merger.
Related errors
- User is from unacceptable issuer
- Failed to decode OpenID Connect id_token: #
- User is from unacceptable domain
- An object of type was hidden due to insufficient scopes on…
- authorization_code_not_supplied
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/725235109f4a4450.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/authentication_provider/microsoft.rb:106
false
end
def login_attribute
raw_login_attribute || "tid+oid"
end
def unique_id(token)
id_token = claims(token)
allowed_tenants = mapped_allowed_tenants
if allowed_tenants.empty? || allowed_tenants.include?("common") || settings["skip_tenant_verification"]
# allow anyone
elsif allowed_tenants.delete("guests")
# just check the issuer
unless allowed_tenants.find { |tenant| id_token["iss"] == "https://login.microsoftonline.com/#{tenant}/v2.0" }
raise OAuthValidationError, t("User is from unacceptable issuer %{issuer}.", issuer: id_token["iss"].inspect)
end
elsif !allowed_tenants.include?(id_token["tid"])
raise OAuthValidationError, t("User is from unacceptable tenant %{tenant}.", tenant: id_token["tid"].inspect)
end
ids = id_token.as_json
ids["tid+oid"] = "#{ids["tid"]}##{ids["oid"]}" if ids["tid"] && ids["oid"]
ids.slice("tid", *self.class.login_attributes)
end
# always process through the multi-valued setter
def tenant=(value)
self.tenants = value
end
def tenants=(value)
value = value.split(",") if value.is_a?(String)
value = value.filter_map(&:strip).uniq
value << "microsoft" if value.delete(MICROSOFT_TENANT)
value = ["common"] if value.include?("common")
self["tenant"] = value.firstView on GitHub (pinned to 1c9f0bb801)