How to verify local macOS user's password using authorization services from Swift?

I'm trying to verify a local macOS user's password using the Authorization services API (via Swift 5) based on an Objective-C example I found.

I've tried every permutation of calls to AuthoricationCreate that I can think of, but they always fail with an OOStatus code of -60005 or -60007

My simple Swift 5 test code is as follows:

import Cocoa import CoreFoundation  class log {     static func debug(_ msg: String) {         Swift.print(msg)         fflush(stdout)     }     static func error(_ msg: String) {         Swift.print(msg)         fflush(stdout)     } }  struct MacAPIRequest_Authenticate {     public var user: String = String()      public var password: String = String()      public init() {} }  var auth: MacAPIRequest_Authenticate = MacAPIRequest_Authenticate() auth.user=CommandLine.arguments[1] auth.password=CommandLine.arguments[2] log.debug("auth=\(auth)")  let authPassword = UnsafeMutablePointer(mutating: (auth.password as NSString).utf8String) log.debug("authPassword=\(String(cString: authPassword!))") let authUser = UnsafeMutablePointer(mutating: (auth.user as NSString).utf8String) log.debug("authUser=\(String(cString: authUser!))") var authEnvItems = [   AuthorizationItem(name: kAuthorizationEnvironmentUsername,                     valueLength: (auth.user as NSString).length, value: authUser, flags: UInt32(0)),   AuthorizationItem(name: kAuthorizationEnvironmentPassword,                     valueLength: (auth.password as NSString).length, value: authPassword, flags: UInt32(0)) ] log.debug("authEnvItems=\(authEnvItems)") var authEnv = AuthorizationEnvironment(count: UInt32(authEnvItems.count), items: &authEnvItems) var authRightsItems = [   AuthorizationItem(name: "allow", valueLength: 0, value: nil, flags: 0) ] var authRights = AuthorizationRights(count: UInt32(authRightsItems.count), items: &authRightsItems) log.debug("authRights=\(authRights)") var authRef: AuthorizationRef? = nil var authStatus: OSStatus var authFlags = AuthorizationFlags(rawValue: 0)  // First try permutation without using AuthorizationCopyRights: authStatus = AuthorizationCreate(&authRights, &authEnv, authFlags, nil) log.debug("0 authStatus=\(authStatus)")  // Using AuthorizationCopyRights: authStatus = AuthorizationCreate(nil, &authEnv, authFlags, &authRef) log.debug("1 authStatus=\(authStatus)") authFlags = AuthorizationFlags([.extendRights, .preAuthorize]) authStatus = AuthorizationCopyRights(authRef!, &authRights, &authEnv, authFlags, nil) log.debug("2 authStatus=\(authStatus)") if (authStatus == errAuthorizationSuccess) {     log.debug("auth(\(auth)) authorized") } else {     log.debug("auth(\(auth)) denied") }

The above was based on what I found here - How verify account's password

Here's what this outputs:

auth=MacAPIRequest_Authenticate(user: "username", password: "password") authPassword=password authUser=username authEnvItems=[__C.AuthorizationItem(name: 0x00007fa52a4df030, valueLength: 8, value: Optional(0x00007fa52a4e9258), flags: 0), __C.AuthorizationItem(name: 0x00007fa52a4df030, valueLength: 8, value: Optional(0x00007fa52a4321d8), flags: 0)] authRights=AuthorizationItemSet(count: 1, items: Optional(0x00007fa52a4c32a0)) 0 authStatus=-60005 1 authStatus=0 2 authStatus=-60007 auth(MacAPIRequest_Authenticate(user: "username", password: "password")) denied

I expect OSStatus of 0 (zero) when providing the correct username/password combination.

Accepted Reply

That’s a very wonky way of authenticating a user. Authorization Services is intended to be used to authorise operations, that is, check whether you can perform some (typically privileged) operation based on rules stored in the authorisation database. Using it to check a username and password is way off-piste.

There are better ways to authenticate a user and, happily, those are much easier to call from Swift. My weapon of choice here is the OpenDirectory framework:

import Foundation
import OpenDirectory

func authenticateLocalUser(username: String, password: String) -> Bool {
    do {
        let session = ODSession()
        let node = try ODNode(session: session, type: ODNodeType(kODNodeTypeLocalNodes))
        let record = try node.record(withRecordType: kODRecordTypeUsers, name: username, attributes: nil)
        try record.verifyPassword(password)
        return true
    } catch {
        return false
    }
}

func main() {
    guard CommandLine.argc == 3 else {
        print("usage: QAuth user password")
        exit(EXIT_FAILURE)
    }
    let success = authenticateLocalUser(username: CommandLine.arguments[1], password: CommandLine.arguments[2])
    if !success {
        print("authentication failed")
        exit(EXIT_FAILURE)
    }
}

main()
exit(EXIT_SUCCESS)

Share and Enjoy

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

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

Replies

My appologies for the unreadable example. I used preformatted text HTML tag, but all the new lines seem to have been removed. There's a readable version of the question here - https://stackoverflow.com/questions/56497686/how-to-verify-local-macos-users-password-using-authorization-services-from-swif

That’s a very wonky way of authenticating a user. Authorization Services is intended to be used to authorise operations, that is, check whether you can perform some (typically privileged) operation based on rules stored in the authorisation database. Using it to check a username and password is way off-piste.

There are better ways to authenticate a user and, happily, those are much easier to call from Swift. My weapon of choice here is the OpenDirectory framework:

import Foundation
import OpenDirectory

func authenticateLocalUser(username: String, password: String) -> Bool {
    do {
        let session = ODSession()
        let node = try ODNode(session: session, type: ODNodeType(kODNodeTypeLocalNodes))
        let record = try node.record(withRecordType: kODRecordTypeUsers, name: username, attributes: nil)
        try record.verifyPassword(password)
        return true
    } catch {
        return false
    }
}

func main() {
    guard CommandLine.argc == 3 else {
        print("usage: QAuth user password")
        exit(EXIT_FAILURE)
    }
    let success = authenticateLocalUser(username: CommandLine.arguments[1], password: CommandLine.arguments[2])
    if !success {
        print("authentication failed")
        exit(EXIT_FAILURE)
    }
}

main()
exit(EXIT_SUCCESS)

Share and Enjoy

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

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

That does exactly what I need. Thanks.

How would I do the same thing for a domain (WIndows Active Directory) user?

How would I do the same thing for a domain (Windows Active Directory) user?

If you want to authenticate any user — that is, do what the login window does when you attempt to log in — change

kODNodeTypeLocalNodes
to
kODNodeTypeAuthentication
.

If you want to authenticate against a specific AD domain, things get a bit trickier, and it’s hard to give you a definitive answer without knowing more about your specific setup. The basic idea is to create an

ODNode
for the AD domain in question and then run the password verification against that node. The bit that varies is how you create that node. You can browse the node hierarchy, hard code a node path, and so on.

Share and Enjoy

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

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