Skip to content

Latest commit

 

History

History
314 lines (249 loc) · 12.9 KB

authentication.md

File metadata and controls

314 lines (249 loc) · 12.9 KB

Authentication

The Box API uses two flavors of OAuth2 for authentication, both of which can can be difficult to implement. The SDK makes it easier by providing classes that handle obtaining tokens and automatically refreshing them when possible. See the Auth Types documentation for a detailed overview of how the Box API handles authentication and how to choose between the different authentication types.

Ways to Authenticate

Developer Token

The fastest way to get started using the API is with developer tokens. A developer token is a short-lived access token that cannot be refreshed and can only be used with your own account. Therefore, they're only useful for testing an app and aren't suitable for use in production. You can obtain a developer token from your application's developer console page.

The following example creates an SDK client with a developer token:

import BoxSDK

let client = BoxSDK.getClient(token: "YOUR_DEVELOPER_TOKEN")

Server Auth with JWT

Server auth allows your application to authenticate itself with the Box API for a given enterprise. By default, your application has a Service Account that represents it and can perform API calls. The Service Account is separate from the Box accounts of the application developer and the enterprise admin of any enterprise that has authorized the app — files stored in that account are not accessible in any other account by default, and vice versa.

JWT Authentication requires a private key for signing the JSON Web Token, which is then exchanged for a valid Box access token that can be used to call the API. It is not secure to store this private key in a mobile app that is publicly distributed, so in order to use JWT Authentication the SDK requires that generation of the access token happens in some other service or process. The SDK can then make a request to that service for access tokens to use.

To use the SDK with JWT Auth, you'll need to provide a block of code that is responsible for requesting a new token when one is needed. This block can call out to a web service or other endpoint that is responsible for generating the token. The block receives the unique ID of the user who needs to be authenticated, and a completion function to call with the results of the authentication.

import BoxSDK

let sdk = BoxSDK(clientId: "YOUR CLIENT ID HERE", clientSecret: "YOUR CLIENT SECRET HERE")

func getTokensFromAuthServer(uniqueID: String, completion: @escaping (Result<AccessTokenTuple, Error>) -> Void) {
    // Call auth service with unique ID; it passes back the access token and time-to-live (TTL) in seconds
    completion(.success((accessToken: accessToken, expiresIn: accessTokenTTLinSeconds)))
}

// Create client with unique ID — note that this can be any value your application understands.  The unique
// ID is a mechanism for your application to keep track of or map your users to Box
sdk.getDelegatedAuthClient(authClosure: getTokensFromAuthServer, uniqueID: "myUser12345") { result in
    switch result {
    case let .success(client):
        // Use client to make API calls
    case let .failure(error):
        // Handle error creating client
    }
}

As an alternative, you can call async version of getDelegatedAuthClient(authClosure:uniqueID:) method:

do {
    let client = try await sdk.getDelegatedAuthClient(authClosure: getTokensFromAuthServer, uniqueID: "myUser12345")
    // Use client to make API calls
}
catch {
    // Handle error creating client
}

Traditional 3-Legged OAuth2

If your application needs to integrate with existing Box users who will provide their login credentials to grant your application access to their account, you will need to go through the standard OAuth2 login flow. A detailed guide for this process is available in the Authentication with OAuth API documentation.

Using an auth code is the most common way of authenticating with the Box API for existing Box users, to integrate with their accounts. The SDK provides a built-in flow for opening a secure web view, into which the user enters their Box login credentials. This requires that the application using the SDK registers itself for a custom URL scheme of the format boxsdk-<<CLIENT ID>>://boxsdkoauth2redirect.

import BoxSDK

let sdk = BoxSDK(clientId: "YOUR CLIENT ID HERE", clientSecret: "YOUR CLIENT SECRET HERE")
sdk.getOAuth2Client() { result in
    switch result {
    case let .success(client):
        // Use client to make API calls
    case let .failure(error):
        // Handle error creating client
    }
}

As an alternative, you can call async version of getOAuth2Client() method:

do {
    let client = try await sdk.getOAuth2Client(tokenStore: KeychainTokenStore())
    // Use client to make API calls
}
catch {
    // Handle error creating client
}

If your application requires a custom URL scheme that differs from the default format boxsdk-<<CLIENT ID>>://boxsdkoauth2redirect, you have the option to pass in a custom callback URL string when creating the SDK client. This is the URL to which Box will redirect the user when authentication completes. This requires that the application using the SDK registers itself for the same custom callback URL.

import BoxSDK

let sdk = BoxSDK(clientId: "YOUR CLIENT ID HERE", clientSecret: "YOUR CLIENT SECRET HERE", callbackURL: "YOUR CALLBACK URL HERE")
sdk.getOAuth2Client() { result in
    switch result {
    case let .success(client):
        // Use client to make API calls
    case let .failure(error):
        // Handle error creating client
    }
}

Client Credentials Grant

Server auth with CCG allows you to obtain an access token by having client credentials and secret with enterprise or user ID, which allows you to work using service or user account. Obtained token is valid for specified amount of time and it will be refreshed automatically by default. A detailed guide for this process is available in the Setup with Client Credentials Grant.

Obtaining Service Account token

By default, your application has a Service Account that represents it and can perform API calls. The Service Account is separate from the Box accounts of the application developer and the enterprise admin of any enterprise that has authorized the app — files stored in that account are not accessible in any other account by default, and vice versa. To obtain service account you will have to provide enterprise ID:

import BoxSDK

let sdk = BoxSDK(clientId: "YOUR CLIENT ID HERE", clientSecret: "YOUR CLIENT SECRET HERE")
sdk.getCCGClientForAccountService(enterpriseId: "YOUR ENTERPRISE ID HERE") { result in
    switch result {
    case let .success(client):
        // Use client to make API calls
    case let .failure(error):
        // Handle error creating client
    }
}

As an alternative, you can call async version of getCCGClientForAccountService(enterpriseId:) method:

do {
    let client = try await sdk.getCCGClientForAccountService(enterpriseId: "YOUR ENTERPRISE ID HERE")
    // Use client to make API calls
}
catch {
    // Handle error creating client
}

Remember that you can still make calls on behalf of managed users, which are part of your enterprise, by using As-User behavior.

Obtaining User token

To obtain client for making calls as an App User or Managed User you will have to provide user ID:

import BoxSDK

let sdk = BoxSDK(clientId: "YOUR CLIENT ID HERE", clientSecret: "YOUR CLIENT SECRET HERE")
sdk.getCCGClientForUser(userId: "YOUR USER ID HERE") { result in
    switch result {
    case let .success(client):
        // Use client to make API calls
    case let .failure(error):
        // Handle error creating client
    }
}

As an alternative, you can call async version of getCCGClientForUser(userId:) method:

do {
    let client = try await sdk.getCCGClientForUser(userId: "YOUR USER ID HERE")
    // Use client to make API calls
}
catch {
    // Handle error creating client
}

Token Store

In order to maintain authentication and ensure that your users do not need to log in again every time they use your application, you should persist their token information to some sort of durable store (e.g. the Keychain). The SDK provides a TokenStore interface which allows you to read and write tokens to whatever store your application uses at the correct points in the SDK's token handling logic. The interface requires three methods:

The SDK provides a default KeychainTokenStore implementation that stores the user's tokens on the device Keychain, but you can also create your own custom token store by implementing something that conforms to the TokenStore protocol:

// The token store constructor should create a specific store instance for the user being authenticated — it may need
// to take in a user ID or other unique identifier

public protocol TokenStore {

    // Read the user's tokens from the data store and pass them to the completion
    func read(completion: @escaping (Result<TokenInfo, Error>) -> Void)

    // Write the token information to the store.  The tokenInfo argument can be serialized for storage.
    // Call the completion after the write has completed.
    func write(tokenInfo: TokenInfo, completion: @escaping (Result<Void, Error>) -> Void)

    // Delete the user's token information from the store, and call the completion after the write.
    func clear(completion: @escaping (Result<Void, Error>) -> Void)
}

As-User

The As-User header is used to make API calls from one user account on behalf of another user. This is used by enterprise administrators and Service Accounts to make API calls on behalf of managed users or app users. This requires the API request to contain an As-User: USER-ID header with each API call, which the SDK handles automatically. For more details, see the documentation on As-User.

The following examples assume that the client has been instantiated with an access token belonging to an admin-role user or Service Account with appropriate privileges to make As-User calls.

The BoxClient#asUser(withId: String) method clones the client to impersonate a given user. Note that a new client is created, and the original client instance is left unmodified. All calls made with the new instance of client will be made in context of the impersonated user.

let asUserClient = client.asUser(withId: "USER ID");
asUserClient.users.getCurrentUser() { result in
    guard case let .success(user) = result else {
        print("Could not retrieve user info!")
        return
    }

    print("Call was made as \(user.name) (\(user.login))")
}

Token Exchange

You can exchange a client's access token for one with a lower scope, in order to restrict the permissions for a child client or to pass to a less secure location (e.g. a browser-based app).

To exchange the token held by a client for a new token with only item_preview scope, restricted to a single file:

client.exchangeToken(scope: ["item_preview"], resource: "https://api.box.com/2.0/files/123456789") { result in
    guard case let .success(tokenInfo) = result else {
        print("Error exchanging tokens")
        return
    }

    print("Got new access token: \(tokenInfo.accessToken)")
}

Revoking Tokens

Access tokens for a client can be revoked when needed. This removes the client's authentication, and the client can no longer be used after this operation.

client.destroy() { result in
    guard case .success = result else {
        print("Tokens could not be revoked!")
    }

    print("Tokens were successfully revoked")
}