{"record":{"id":"d1eb2985ff9381a8","repo":"crowdsecurity/crowdsec","slug":"tls-authentication-required","errorCode":null,"errorMessage":"tls authentication required","messagePattern":"tls authentication required","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"pkg/apiserver/middlewares/v1/jwt.go","lineNumber":62,"sourceCode":"\tmachineID := claims[MachineIDKey].(string)\n\n\treturn &models.WatcherAuthRequest{\n\t\tMachineID: &machineID,\n\t}\n}\n\ntype authInput struct {\n\tmachineID      string\n\tclientMachine  *ent.Machine\n\tscenariosInput []string\n}\n\nfunc (j *JWT) authTLS(c *gin.Context) (*authInput, error) {\n\tctx := c.Request.Context()\n\tret := authInput{}\n\n\tif j.TlsAuth == nil {\n\t\terr := errors.New(\"tls authentication required\")\n\t\tlog.Warn(err)\n\n\t\treturn nil, err\n\t}\n\n\textractedCN, err := j.TlsAuth.ValidateCert(c)\n\tif err != nil {\n\t\tlog.Warn(err)\n\t\treturn nil, err\n\t}\n\n\tlogger := log.WithField(\"ip\", c.ClientIP())\n\n\tret.machineID = fmt.Sprintf(\"%s@%s\", extractedCN, c.ClientIP())\n\n\tret.clientMachine, err = j.DbClient.Ent.Machine.Query().\n\t\tWhere(machine.MachineId(ret.machineID)).\n\t\tFirst(ctx)","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/apiserver/middlewares/v1/jwt.go#L44-L80","documentation":"JWT.authTLS handles mTLS client-certificate authentication for LAPI. It returns \"tls authentication required\" when j.TlsAuth is nil, i.e. the JWT middleware was constructed without TLS auth wired up even though the request arrived via the TLS-auth path. Normally NewJWT sets TlsAuth: &TLSAuth{}, so nil indicates a manually built JWT or a modified construction path.","triggerScenarios":"A request authenticated via client certificate reaches Authenticator -> authTLS while the JWT struct's TlsAuth field is nil — e.g. JWT built with &JWT{} instead of NewJWT, or code that nils TlsAuth when TLS is disabled but the server still presents/receives a client cert.","commonSituations":"Custom embedding of crowdsec's LAPI in another binary; config where api.tls is partially set so the server requests a client cert but the middleware lacks TlsAuth; tests constructing JWT{} directly.","solutions":["Construct the middleware with NewJWT(dbClient) so TlsAuth is initialized","If embedding, replicate NewJWT's setup: TlsAuth: &TLSAuth{}","Enable client-certificate auth properly in lapi.yaml under api.server.tls and restart crowdsec","Ensure clients not using mTLS fall back to API-key auth and don't hit the TLS path"],"exampleFix":"// before\njwtMiddleware := &JWT{DbClient: dbClient}\n// after\njwtMiddleware, err := NewJWT(dbClient) // sets TlsAuth: &TLSAuth{}","handlingStrategy":"validation","validationCode":"// before relying on mTLS auth, confirm the middleware has TLS auth wired:\nif j.TlsAuth == nil {\n    return errors.New(\"TLS auth middleware not initialized; use NewJWT\")\n}","typeGuard":null,"tryCatchPattern":"authInput, err := j.Authenticator(c)\nif err != nil {\n    log.WithError(err).Warn(\"authentication failed\")\n    c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{\"message\": \"authentication failed\"})\n    return\n}","preventionTips":["Always build the JWT middleware via NewJWT(dbClient), never with a bare struct literal","Configure api.server.tls in lapi.yaml before enabling client-cert auth on clients","Keep API-key auth available as the fallback path for non-mTLS clients"],"tags":["tls","mtls","authentication","lapi"],"backgroundTag":"authentication-required","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}