{"record":{"id":"3ea10dd9766a9fe1","repo":"ory/hydra","slug":"expected-request-to-be-of-type-session-but-got-3ea10d","errorCode":null,"errorMessage":"Expected request to be of type *Session, but got: %T","messagePattern":"Expected request to be of type \\*Session, but got: %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"persistence/sql/persister_oauth2.go","lineNumber":259,"sourceCode":"\t} else if err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *Persister) sqlSchemaFromRequest(ctx context.Context, signature string, r fosite.Requester, table tableName, expiresAt time.Time) (*OAuth2RequestSQL, error) {\n\tsubject := \"\"\n\tif r.GetSession() == nil {\n\t\tp.l.Debugf(\"Got an empty session in sqlSchemaFromRequest\")\n\t} else {\n\t\tsubject = r.GetSession().GetSubject()\n\t}\n\n\tvar challenge sql.NullString\n\trr, ok := r.GetSession().(*oauth2.Session)\n\tif !ok && r.GetSession() != nil {\n\t\treturn nil, errors.Errorf(\"Expected request to be of type *Session, but got: %T\", r.GetSession())\n\t} else if ok {\n\t\tif len(rr.ConsentChallenge) > 0 {\n\t\t\tchallenge = sql.NullString{Valid: true, String: rr.ConsentChallenge}\n\t\t}\n\t}\n\n\tsession, err := json.Marshal(rr)\n\tif err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\n\tif p.r.Config().EncryptSessionData(ctx) {\n\t\tciphertext, err := p.r.KeyCipher().Encrypt(ctx, session, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsession = []byte(ciphertext)\n\t}","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/persistence/sql/persister_oauth2.go#L241-L277","documentation":"sqlSchemaFromRequest (persistence/sql/persister_oauth2.go) converts a fosite Requester to a database row and requires the session to be the concrete *oauth2.Session type so it can persist ConsentChallenge. When GetSession() is non-nil but has a different Go type, it returns this error naming the offending type. This blocks createSession and CreateRefreshTokenSession from persisting the flow.","triggerScenarios":"A custom session type registered via a claims/session extension (oauth2.session config, custom SessionStorage) flows into the authorization code or refresh token grant; manual construction of fosite requests with a different session struct; plugins/SDK code calling persister functions with foreign session objects.","commonSituations":"Projects customizing ID/access token claims with their own session struct and hitting refresh-token flows; forking Hydra and changing the session type without adapting the SQL persister; test fixtures using mock sessions.","solutions":["Ensure *oauth2.Session is used for all authorization-code/refresh-token requests (extend it for custom claims rather than replacing it).","If a custom session type is required, also fork/adapt the SQL persister to handle it.","Review config under oauth2.session / claims-mapping that installs a different session factory and align it with *oauth2.Session.","Fix request-building code or tests to instantiate oauth2.NewSession(...)."],"exampleFix":"// before\nrequest.SetSession(mySession{Subject: sub})\n// after\nsess := oauth2.NewSession(clientID)\nsess.Subject = sub\nrequest.SetSession(sess)","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isOAuth2Session(r fosite.Requester) bool {\n    _, ok := r.GetSession().(*oauth2.Session)\n    return ok\n}","tryCatchPattern":null,"preventionTips":["Use *oauth2.Session for all code/refresh grant requests; extend it for custom claims.","If forking, adapt the SQL persister together with any custom session type.","Keep test fixtures on the real oauth2.Session type or add the guard above in harnesses."],"tags":["go","type-mismatch","oauth2","refresh-token"],"backgroundTag":"unexpected-session-type","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}