Pre-authorization vs Authorization

In Authorization Services Programming Guide, it takes the Factored Applications as an example that is using pre-authorization and authorization together.

However, I didn't fully understand the exact difference between Pre-authorization and Authorization. Why I need to do pre-auth (rather than auth) in the application side before creating the external authorization reference?

Accepted Reply

If you ignore the existing of the pre-authorisation concept for the moment, things work as follows. The right specification has a timeout, which is the maximum age of a credential that will satisfy the right. If that’s 0, the right can only be satisfied by freshly acquired credentials, meaning that it always presents an authorisation UI to the user.

Pre-authorisation changes this as follows:

  1. The pre-authorisation triggers the UI, gets the necessary credentials, and satisfies the right

  2. It remembers that it satisfied the right

  3. Later on, when you do the normal authorisation, it uses that fact to allow the normal authorisation to succeed without triggering the UI

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

I'd imagine one example includes creating an account authorization where the other assumes one already exists.

From the docs (and remembering besides pre, we also have partial):

https://developer.apple.com/documentation/security/1395770-authorizationcopyrights?preferredLanguage=occ


There are three main reasons to use this function. The first reason is to preauthorize rights by specifying the

kAuthorizationFlagPreAuthorize
,
kAuthorizationFlagInteractionAllowed
, and
kAuthorizationFlagExtendRights
masks as authorization options. Preauthorization is most useful when a right has a zero timeout. For example, you can preauthorize in the application and if it succeeds, call the helper tool and request authorization. This eliminates calling the helper tool if the Security Server cannot later authorize the specified rights.


The second reason to use this function is to authorize rights before performing a privileged operation by specifying the

kAuthorizationFlagInteractionAllowed
, and
kAuthorizationFlagExtendRights
masks as authorization options.


The third reason to use this function is to authorize partial rights. By specifying the

kAuthorizationFlagPartialRights
,
kAuthorizationFlagInteractionAllowed
, and
kAuthorizationFlagExtendRights
masks as authorization options, the Security Server grants all rights it can authorize. On return, the authorized set contains all the rights.

First up, I wouldn’t get too hung up on pre-authorisation: It’s entirely optional, and lots of folks have used Authorization Services without ever using it.

Second, I want to cover the big picture here, namely: Why should you pre-authorise in your app and then authorise in your helper tool? You do this because it gives you control over the timing and context of any authorisation dialog that comes up as a result of your authorisation request. Imagine you’re implementing an install operation. This has multiple steps, including:

  1. Download the content to install

  2. Unpack it

  3. Move it to a privileged place

  4. Verify it authenticity

  5. Install it

Steps 1 and 2 are done by your app; steps 3 through 5 are done by your privileged code. Steps 1 and 2 can take a while, for example, if you have a slow network. However, you want the authorisation dialog to show up before step 1, not before step 3. To achieve this you have your app pre-authorise before step 1 and then have your privileged code do the final authorisation before step 3.

Finally, let’s look at the difference between authorisation and pre-authorisation. You wrote:

For a right has a zero timeout, can I just request authorization (instead of preautho) in the application and if it succeeds, call the helper tool and request authorization?

If a right specification has a zero timeout then it won’t use old credentials. So, if you authorise in your app and then re-authorise in your privileged code, the user will have to authorise twice. In contrast, pre-authorisation lets you put up the authorisation dialog in step 1 but tells the system that you’re not actually ‘consuming’ the authorisation at that time. That’s deferred until your privileged code does the real authorisation before step 3. You can reasonably expect that authorisation to not put any UI because the right was pre-authorised.

IMPORTANT This last point is not a guarantee; you have to be able to cope with the authorisation request triggering an authorisation dialog.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you, Eskimo. The example makes sense.

Can I ask more about the meaning of "not actually ‘consuming’ the authorisation" mentioned in your last second paragraph? Does that mean the timeout of right or credential?

It appears to me that the difference between the pre-auth and auth is about the actual "consuming" of the right. If I can understand how does pre-auth not actually consuming the authorization, I can understand why a pre-auth is better than a direct auth here.

If you ignore the existing of the pre-authorisation concept for the moment, things work as follows. The right specification has a timeout, which is the maximum age of a credential that will satisfy the right. If that’s 0, the right can only be satisfied by freshly acquired credentials, meaning that it always presents an authorisation UI to the user.

Pre-authorisation changes this as follows:

  1. The pre-authorisation triggers the UI, gets the necessary credentials, and satisfies the right

  2. It remembers that it satisfied the right

  3. Later on, when you do the normal authorisation, it uses that fact to allow the normal authorisation to succeed without triggering the UI

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you Quinn for this explanation it definitly helps understanding the authorisation chain between app and helper tool.


However I still have a question regarding privileged helpers (I'm talking about Launch Daemons implemented as in 'EvenBetterAuthorizationSample').


My understanding is that the daemonized helper, running as root, will always be granted rights without UI.

However I have cases where it fails. For example when using SystemConfiguration framework.


Calling SCPreferencesSetComputerName for example will always prompt an UI from privileged helper tool except if has just been installed (SMJobBless authentification UI from host app).


Are there APIs that cannot 'inherit' root rights from privileged helper tools ?

My understanding is that the daemonized helper, running as root, will always be granted rights without UI.

That’s not correct. The right specification can opt in to being satisfied by the caller being root (via the

allow-root
requirement) but, by default, root has no special privileges.

Calling

SCPreferencesSetComputerName
for example will always prompt an UI from privileged helper tool except if has just been installed (
SMJobBless
authentification UI from host app).

The reason why the ‘just installed’ case works is that the cached credential from the installation satisfies the

group
/
admin
requirement.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

If I use "security authorizationdb read 'system.services.systemconfiguration.network'"' I can verify that 'allow-root' flag is true.

Is this flag enough to enable non-prompted privileged helper tool (regardless for example of 'shared' flag) ?

If yes it means that I do something wrong in my authentification code.


// Part of privileged helper tool
// Based on Apple sample code: https://developer.apple.com/library/content/samplecode/EvenBetterAuthorizationSample
NSError*    error = NULL;
AuthorizationRef    authRef = NULL;

// Create an authorization ref from that the external form data contained within
OSStatus    status = AuthorizationCreateFromExternalForm([authData bytes], &authRef);

// Authorize the right associated with the command
if (status == errAuthorizationSuccess) {
    AuthorizationItem   oneRight = { NULL, 0, NULL, 0 };
    AuthorizationRights rights = { 1, &oneRight };

    oneRight.name = "system.services.systemconfiguration.network";
    status = AuthorizationCopyRights(authRef, &rights, kAuthorizationEmptyEnvironment, kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, NULL);
    // At this stage an UI is prompted from privileged helper tool
}       

if (status == errAuthorizationSuccess) {
    SCPreferencesRef    prefs = SCPreferencesCreateWithAuthorization(NULL, (CFStringRef)processName, NULL, authRef);
    if (SCPreferencesLock(prefs, YES)) {
        SCPreferencesSetComputerName(prefs, (CFStringRef)hostname, kCFStringEncodingUTF8);
        SCPreferencesCommitChanges(prefs);
        SCPreferencesUnlock(prefs);
    }
}
// At this stage an UI is prompted from privileged helper tool

Given the context of this thread I’m presuming that the user space side of this code is still pre-authorising. Is that right? If so, if you remove the pre-authorisation does that prevent the UI here?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Adding or removing the pre-authorize flag on main app doesn't change the behaviour.

I added the whole workflows that work or fails to provide more context :



// This workflow (lets call it 'A') succeeds:
// - user is prompted for admin password in main app
// - rights are forwarded to helper tool through XPC (based on 'EBAS' sample code)
// - SystemConfiguration is used to change machine name
// - Other privileged API can be used (e.g CFPreferences for /Library prefs)
//
// Main app
AuthorizationItem   oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = "system.services.systemconfiguration.network";

AuthorizationExternalForm   extForm;
OSStatus    status = AuthorizationCreate(&rights, NULL, kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, &self->_authRef);
if (status == errAuthorizationSuccess) {
  // At this stage the user has been prompted and entered a valid admin password
  status = AuthorizationMakeExternalForm(self->_authRef, &extForm);
}

if (status == errAuthorizationSuccess) {
   authData = [[NSData alloc] initWithBytes:&extForm length:sizeof(extForm)];
}


// Helper tool through XPC
AuthorizationItem   oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = "system.services.systemconfiguration.network";

OSStatus    status = AuthorizationCreateFromExternalForm([authData bytes], &authRef);
if (status == errAuthorizationSuccess) {
  status = AuthorizationCopyRights(authRef, &rights, NULL, kAuthorizationFlagExtendRights, NULL);
}

if (status == errAuthorizationSuccess) {
  // This code is granted rights for System Configuration after user prompt from main app
  SCPreferencesRef    prefs = SCPreferencesCreateWithAuthorization(NULL, (CFStringRef)processName, NULL, authRef);
  if (SCPreferencesLock(prefs, YES)) {
  SCPreferencesSetComputerName(prefs, (CFStringRef)hostname, kCFStringEncodingUTF8);
  SCPreferencesCommitChanges(prefs);
  SCPreferencesUnlock(prefs);
  }
}

// This code always succeed from helper tool regardless of authorizations from Security.framework
CFStringRef suite = CFSTR("com.company.sample");
CFPreferencesSetValue(CFSTR("myKey"), (__bridge CFStringRef)(myValue), suite, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
CFPreferencesSynchronize(suite, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);


// This workflow (lets call it 'B') succeeds:
// - main app is launched with root user (e.g using xCode 'root' setup in debug scheme)
// - main app authorize implicitly with no UI (inherit rights from root)
// - helper tool get rights with no prompt UI
//
// Main app
AuthorizationItem   oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = "system.services.systemconfiguration.network";

AuthorizationExternalForm   extForm;
OSStatus    status = AuthorizationCreate(&rights, NULL, kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights, &self->_authRef);
if (status == errAuthorizationSuccess) {
  status = AuthorizationMakeExternalForm(self->_authRef, &extForm);
}


// Helper tool through XPC
AuthorizationItem   oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = "system.services.systemconfiguration.network";

OSStatus    status = AuthorizationCreateFromExternalForm([authData bytes], &authRef);
if (status == errAuthorizationSuccess) {
  status = AuthorizationCopyRights(authRef, &rights, NULL, kAuthorizationFlagExtendRights, NULL);
}

if (status == errAuthorizationSuccess) {
  // This code is granted rights for System Configuration without any prompt
  SCPreferencesRef    prefs = SCPreferencesCreateWithAuthorization(NULL, (CFStringRef)processName, NULL, authRef);
  if (SCPreferencesLock(prefs, YES)) {
  SCPreferencesSetComputerName(prefs, (CFStringRef)hostname, kCFStringEncodingUTF8);
  SCPreferencesCommitChanges(prefs);
  SCPreferencesUnlock(prefs);
  }
}

// This code always succeed from helper tool regardless of authorizations from Security.framework
CFStringRef suite = CFSTR("com.company.sample");
CFPreferencesSetValue(CFSTR("myKey"), (__bridge CFStringRef)(myValue), suite, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
CFPreferencesSynchronize(suite, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);


// This workflow (lets call it 'C') fails:
// - main app doesn't authorize nor pre-authorize
// - helper tool get rights with no prompt UI
//
// Main app
AuthorizationExternalForm   extForm;
OSStatus    status = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (status == errAuthorizationSuccess) {
  status = AuthorizationMakeExternalForm(self->_authRef, &extForm);
}

if (status == errAuthorizationSuccess) {
   authData = [[NSData alloc] initWithBytes:&extForm length:sizeof(extForm)];
}


// Helper tool through XPC
AuthorizationItem   oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = "system.services.systemconfiguration.network";

OSStatus    status = AuthorizationCreateFromExternalForm([authData bytes], &authRef);

if (status == errAuthorizationSuccess) {
  status = AuthorizationCopyRights(authRef, &rights, NULL, kAuthorizationFlagExtendRights, NULL);
  // At this stage errAuthorizationInteractionNotAllowed is returned
}

if (status == errAuthorizationSuccess) {
  // Going ahead regardless of previous error will prompt an authentication request
  SCPreferencesRef    prefs = SCPreferencesCreateWithAuthorization(NULL, (CFStringRef)processName, NULL, authRef);
  if (SCPreferencesLock(prefs, YES)) {
  SCPreferencesSetComputerName(prefs, (CFStringRef)hostname, kCFStringEncodingUTF8);
  SCPreferencesCommitChanges(prefs);
  SCPreferencesUnlock(prefs);
  }
}

// This code always succeed from helper tool regardless of authorizations from Security.framework
CFStringRef suite = CFSTR("com.company.sample");
CFPreferencesSetValue(CFSTR("myKey"), (__bridge CFStringRef)(myValue), suite, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
CFPreferencesSynchronize(suite, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);

You wrote:

// This workflow (lets call it 'C') fails:
// - main app doesn't authorize nor pre-authorize
// - helper tool get rights with no prompt UI

How is that failure? I thought the goal was to not get an auth prompt in this case, and that’s what’s happening.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"

Sorry my wording is confusing. To be clearer:


Expected results:

  • main app doesn't authorize nor pre-authorize
  • helper tool get rights with no prompt UI


Actual results:

  • main app doesn't authorize nor pre-authorize
  • helper tool can't get rights without prompt (AuthorizationCopyRights returns errAuthorizationInteractionNotAllowed)
  • SCPreferencesSetComputerName prompt an UI

Thanks for the clarification. I’m not sure why

allow-root
is not working in this case.

What happens if you skip authorisation entirely, that is, call

SCPreferencesCreate
in your helper tool?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

This sounds crazy but it works !

Should I file a bug report ?

This sounds crazy …

That’s not crazy at all. Look at the release vehicles for these two APIs:

  • SCPreferencesCreate
    was released with the introduction of the framework in 10.1
  • SCPreferencesCreateWithAuthorization
    was released well after that, in 10.5

Originally

SCPreferences
required you to be running as root in order to commit changes and there was no way around that.
SCPreferencesCreateWithAuthorization
was added to allow non-root code to make changes subject to authorisation, thus avoiding the entire privileged helper tool launched as a launchd daemon dance. However, if you’re already running as root there’s no need for that.

Whether this is the right solution depends on your specific requirement. Do you have other reasons to be running privileged code? If so,

SCPreferencesCreate
is the right answer. In that case you should probably add your own authorization right that accurately describes the high-level semantics of your privileged code as a whole.

If, however, you don’t have any other privileged stuff to do, you could remove your entire launchd subsystem and just rely on

SCPreferencesCreateWithAuthorization
.

As to the specifics of why the authorisation being done

SCPreferencesCreateWithAuthorization
isn’t honouring
allow-root
, that is a bit of a mystery. However, I’m not sure it’s a mystery you need to solve.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"