instructure/canvas-lms · warning
Attachment verifier token expired: #
Error message
Attachment verifier token expired: #{verifier} What it means
When CanvasSecurity.decode_jwt raises CanvasSecurity::TokenExpired, the attachment verifier JWT has passed its expiry. decode_verifier logs this warning with the raw token, increments the attachments.token_verifier_expired metric, and returns nil, denying access. Verifiers are short-lived by design.
Solutions
- Regenerate the download URL so a fresh verifier is issued (re-request the file's download/inline URL)
- Increase the verifier TTL if legitimate links expire too quickly (CanvasSecurity JWT TTL config)
- Avoid persisting verifier URLs in emails/caches; link to a page that mints a fresh verifier
- Tell users to re-open the file from Canvas UI rather than reusing old links
Example fix
// before <a href="<%= file.download_url %>"> # embedded with expired verifier // after <a href="<%= course_file_path(context, file) %>"> # fresh verifier minted per request
Defensive patterns
Strategy: validation
Validate before calling
def verifier_expired?(verifier) payload = CanvasSecurity.decode_jwt(verifier) rescue nil payload.nil? # decode_verifier returns nil for expired tokens end
Type guard
def fresh_verifier?(verifier, ttl: 5.minutes) payload = CanvasSecurity.decode_jwt(verifier) rescue nil payload.is_a?(Hash) && payload[:exp].to_i > Time.now.to_i end
Try / catch
begin body = CanvasSecurity.decode_jwt(verifier) rescue CanvasSecurity::TokenExpired redirect_to fresh_download_url(attachment) nil end
Prevention
- Do not persist verifier URLs beyond the token TTL (emails, caches, bookmarks)
- Mint verifiers at request time via download/inline URL helpers
- Tune the verifier TTL to match realistic link-lifetime expectations
- Monitor attachments.token_verifier_expired metrics to spot TTL misconfigurations
When it happens
Trigger: A user follows a download/inline link whose verifier JWT was generated earlier than the token TTL (e.g. a link emailed or bookmarked, opened after expiration).
Common situations: Emailed file links opened days later; cached HTML pages embedding expired verifiers; long-running browser sessions reusing old URLs; overly short verifier TTL settings.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Attachment verifier token id mismatch. token id: #
- Attachment verifier token invalid: #
- Cannot generate a services JWT without a 'sub' entry
- masquerading user not found
- refresh window exceeded
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/3160841602580d5e.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/attachments/verification.rb:87
# probably want to use `valid_verifier_for_permission?`.
#
# @param verifier (String) - The verifier
#
# Returns nil if the verifier could not be decoded for whatever reason, and returns
# a Hash of the body contents if it can.
def decode_verifier(verifier)
begin
body = CanvasSecurity.decode_jwt(verifier)
if body[:id] != attachment.global_id
InstStatsd::Statsd.distributed_increment("attachments.token_verifier_id_mismatch")
Rails.logger.warn("Attachment verifier token id mismatch. token id: #{body[:id]}, attachment id: #{attachment.global_id}, token: #{verifier}")
return nil
end
InstStatsd::Statsd.distributed_increment("attachments.token_verifier_success")
rescue CanvasSecurity::TokenExpired
InstStatsd::Statsd.distributed_increment("attachments.token_verifier_expired")
Rails.logger.warn("Attachment verifier token expired: #{verifier}")
return nil
rescue CanvasSecurity::InvalidToken
InstStatsd::Statsd.distributed_increment("attachments.token_verifier_invalid")
Rails.logger.warn("Attachment verifier token invalid: #{verifier}")
return nil
end
body
end
# Decodes a verifier and checks the user of the verifier has permission to access
# the attachment.
#
# @param verifier (String) - The verifier
# @param permission (Symbol) - Either :read or :download
#
# Returns a boolean
def valid_verifier_for_permission?(verifier, permission, root_account, session = {}, request: nil, files_domain: false)View on GitHub (pinned to 1c9f0bb801)