instructure/canvas-lms · error · ArgumentError
Cannot decode nil token string
Error message
Cannot decode nil token string
What it means
CanvasSecurity::ServicesJwt wraps a raw JWT string for decoding. A nil token string cannot be decoded, so the constructor immediately raises ArgumentError rather than failing later inside JWT parsing. This is a fail-fast guard at the API boundary.
Solutions
- Check the token for nil before constructing ServicesJwt and return 401/handle the missing-token case
- Fix the token extraction so empty/missing tokens are rejected earlier
- Use safe navigation with a fallback or early return in the controller
Example fix
// before
token = CanvasSecurity::ServicesJwt.new(request.headers['Authorization']&.split(' ')&.last)
// after
raw = request.headers['Authorization']&.split(' ')&.last
return render json: { error: 'missing token' }, status: :unauthorized if raw.blank?
token = CanvasSecurity::ServicesJwt.new(raw) Defensive patterns
Strategy: type-guard
Validate before calling
raise ArgumentError, 'token required' if token_string.nil? || token_string.empty?
Type guard
def present_token?(raw) = raw.is_a?(String) && !raw.empty?
Try / catch
begin CanvasSecurity::ServicesJwt.new(raw) rescue ArgumentError respond_missing_token end
Prevention
- Check Authorization headers for nil/blank before parsing
- Use safe navigation and early returns in controllers
- Add request specs covering missing-token paths
When it happens
Trigger: Calling ServicesJwt.new(nil) or ServicesJwt.new(nil, wrapped: false); passing a variable from request headers/session that is nil (e.g. Authorization header missing).
Common situations: Extracting a token with `request.headers['Authorization']&.split(' ')&.last` which yields nil when the header is absent or malformed; cached lookups returning nil.
Related errors
- Cannot generate a symmetric, non-encrypted JWT
- invalid token
- Must have a domain and a user to build a JWT
- A new_id, '# ', referenced an existing # and the # with #…
- A new_integration_id, '#
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/118a9824de36cc25.
Report an issue: GitHub.
Appendix: source
Thrown at gems/canvas_security/lib/canvas_security/services_jwt.rb:33
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
class CanvasSecurity::ServicesJwt
KeyStorage = CanvasSecurity::KeyStorage.new("services-jwt")
class InvalidRefresh < RuntimeError; end
REFRESH_WINDOW = 6.hours
DEFAULT_AUDIENCE = "Instructure"
attr_reader :token_string, :is_wrapped
def initialize(raw_token_string, wrapped: true)
@is_wrapped = wrapped
if raw_token_string.nil?
raise ArgumentError, "Cannot decode nil token string"
end
@token_string = raw_token_string
end
def wrapper_token
return {} unless is_wrapped
raw_wrapper_token = CanvasSecurity.base64_decode(token_string)
keys = [signing_secret]
keys << previous_signing_secret if previous_signing_secret
CanvasSecurity.decode_jwt(raw_wrapper_token, keys)
end
def original_token(ignore_expiration: false)
original_crypted_token = if is_wrapped
wrapper_token[:user_token]
elseView on GitHub (pinned to 1c9f0bb801)