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

  1. Make the session passed into the device flow an instance of *oauth2.Session (or embed and convert it before calling the persister).
  2. If using a custom session for claim extension, keep the consent challenge accessible by wrapping/converting to *oauth2.Session before persistence.
  3. Check config for custom session/claim extensions (e.g. oauth2.session) and remove or adapt them when using the device code flow.
  4. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/0e3291b699b0e090. Report an issue: GitHub.