ory/hydra · error
Expected request to be of type *Session, but got: %T
Error message
Expected request to be of type *Session, but got: %T
What it means
sqlDeviceSchemaFromRequest converts a fosite Requester into a database row and expects the attached session to be of the concrete type *oauth2.Session (so it can read ConsentChallenge). If GetSession() returns a non-nil value of any other Go type, it returns this error with the actual %T type name. It indicates that a custom session implementation was plugged into the OAuth2 flow but the SQL persister cannot serialize it for the device code flow.
Source
Thrown at persistence/sql/persister_device.go:128
}
session, err := json.Marshal(r.GetSession())
if err != nil {
return nil, errors.WithStack(err)
}
if p.r.Config().EncryptSessionData(ctx) {
ciphertext, err := p.r.KeyCipher().Encrypt(ctx, session, nil)
if err != nil {
return nil, errors.WithStack(err)
}
session = []byte(ciphertext)
}
var challenge sql.NullString
rr, ok := r.GetSession().(*oauth2.Session)
if !ok && r.GetSession() != nil {
return nil, errors.Errorf("Expected request to be of type *Session, but got: %T", r.GetSession())
} else if ok {
if len(rr.ConsentChallenge) > 0 {
challenge = sql.NullString{Valid: true, String: rr.ConsentChallenge}
}
}
return &DeviceRequestSQL{
Request: r.GetID(),
ConsentChallenge: challenge,
ID: deviceCodeSignature,
UserCodeID: userCodeSignature,
RequestedAt: r.GetRequestedAt(),
InternalExpiresAt: sqlxx.NullTime(expiresAt),
Client: r.GetClient().GetID(),
Scopes: strings.Join(r.GetRequestedScopes(), "|"),
GrantedScope: strings.Join(r.GetGrantedScopes(), "|"),
GrantedAudience: strings.Join(r.GetGrantedAudience(), "|"),
RequestedAudience: strings.Join(r.GetRequestedAudience(), "|"),View on GitHub (pinned to 4174065ffb)
Solutions
- Make the session passed into the device flow an instance of *oauth2.Session (or embed and convert it before calling the persister).
- If using a custom session for claim extension, keep the consent challenge accessible by wrapping/converting to *oauth2.Session before persistence.
- Check config for custom session/claim extensions (e.g. oauth2.session) and remove or adapt them when using the device code flow.
- Fix test/integration harnesses to construct requests with the correct session type.
Example fix
// before
req.SetSession(&myCustomSession{Subject: "user"})
// after
sess := oauth2.NewSession("client-id")
sess.Subject = "user"
req.SetSession(sess) Defensive patterns
Strategy: type-guard
Type guard
func isOAuth2Session(r fosite.Requester) bool {
_, ok := r.GetSession().(*oauth2.Session)
return ok
}
// call before invoking CreateDeviceAuthSession Prevention
- Always build device-flow requests with oauth2.NewSession.
- Extend *oauth2.Session for custom claims instead of substituting a new type.
- Audit any config that installs a custom session factory when using the device code flow.
When it happens
Trigger: Registering a custom session type via config (oauth2.session.establish_session or a custom SessionStorage/transform extension) and then calling CreateDeviceAuthSession or UpdateDeviceCodeSessionBySignature with that request; running device authorization flow while a custom JWT/session claims extension swapped the session type.
Common situations: Projects that customize access/id token claims with their own session struct but later enable the device flow; integration code that constructs a fosite request manually with the wrong session type; tests passing a mock session into the persister.
Related errors
- Expected request to be of type *Session, but got: %T
- invalid_request
- device_challenge is required
- issuer URL must be set unless development mode is enabled
- issuer URL scheme must be HTTPS unless development mode is e
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/0e3291b699b0e090.
Report an issue: GitHub.