I ended up coding this solution - posting it here in case it can help others (or in case others have suggestions on improving it).
Here's some of the logic:
The app was originally built with Clearance for authentication/authorization, so sticking with Clearance allows existing names/pwds and existing authorization code to keep working.
User identification
Clearance uses email address as the primary identifier. The app needs each user to have an email address for other purposes, so we'll continue to use email as primary user id. We grab it from FB when the user registers, if they register via FB. (note that omniauth-facebook's requests a configurable set of FB permissions; access to the email address is included by default).
User registration
New users have the choice of creating an email/pwd combo, or of registering through FB. Omniauth-facebook is used to authenticate against FB (and to allow for extension to other auth systems over time). We get user data (name, email, etc) back from FB, plus a Facebook auth token. Users authenticated this way do not need to provide a password. Users choosing to register without FB provide an email address, password, and other user data. Users created by FB login are taken to user/edit to finish providing any profile details that we can't grab from FB. We also keep the existing user registration mechanism, allowing the user to provide email/pwd/other details manually.
User validations
Clearance validates the user's email address. Our overridden password_optional? function essentially bypasses their password validation. For production use, this solution should include user validations to implement 'you have to have at least one of a valid pwd or valid omniauth creds"
Session creation
Clearance's session model is used (storing a remember_token in a cookie).
Session controller is overridden to add a method for signing via FB. Callback from FB routes to this method, which creates/updates user's auth data and calls Clearance's sign_in(user)
Authorization
Clearance's simple model is preserved: 'authorize' filter essentially just checks that a valid user is signed in, and a current_user helper is provided.
FB usage
The user's FB token is stored after FB authentication (in an authentication object that belongs_to the user). Koala is used for other FB requests (posting to the user's wall, for example)...details omitted here; i'm not doing anything special.
FB token refresh
FB tokens expire periodically (and FB's "offline access" role is deprecated). The token is refreshed when users log in, but token may become invalid before the app session expires (when the user logs out of FB, changes their FB password, or the token expires). I'm working on how to periodically update the FB token outside the sign-in flow, but that seems out of scope for this answer.