Skip to content

Latest commit

 

History

History
361 lines (257 loc) · 11.7 KB

File metadata and controls

361 lines (257 loc) · 11.7 KB
title Providers
permalink /providers

Providers

Providers are used to fetch account resources from their persistence layer or source, such as a third party API or a storage layer like MongoDB, Redis, or MySQL, for instance.

There are a couple built-in providers that you can take advantage of, or you can use a service as your provider.

The built-in providers allow you to use configuration instead of actually coding your own solutions.

Each built-in provider packaged with this bundle allows you configure an option for logging a user out when the password changes. To use this option, specify change_password_logout: true inside your provider config.

In-Memory Provider

The In-Memory provider allows you to use your config file as the persistence layer. The username, password, and user roles are all configured inside the provider configuration file, so you do not need any other database or file storage.

security:

    providers:
    
        admin_memory_provider:
            memory:
                users:
                    super_admin:
                        password: super-admin-password
                        roles: [ROLE_USER, ROLE_ADMIN, ROLE_SUPER_ADMIN]
 
                    admin:
                        password: admin-password
                        roles: [ROLE_USER, ROLE_ADMIN]
        
        front_memory_provider:
            memory:
                users:
                    moderator:
                        password: moderator-password
                        roles: [ROLE_USER, ROLE_MODERATOR]

In the above example, you can see the password is specified in plain text. You would do this if you are using the 'text' encryptor. Although, it's advised that you use a salt / pepper, and base64 encode it, if this is your approach. The username is the object key, super_admin, admin and moderator, in this case.

You can specify as many groups of in-memory users as you want, and you can specify as many users in each group as you want. You might separate them into groups so that you can take advantage of provider chains, but you can very well just put all users into one group.

You are not restricted to using the text encryptor. You can use any registered encryptor that you wish. The catch is that you need to encrypt the password yourself and store the encrypted value in the config file. You can use the conga security:encrypt command to do this.

The users that get resolved from the In-Memory provider will be an instance of @conga/framework-security:security/user/InMemoryUser. This is so that you can configure the in-memory user's encryption strategy separately from other user instances. Below is an example using the bcrypt encryptor with the InMemoryUser.

security:

    encryption:
    
        # the object key, 'in_memory_user', can be any name you want
        in_memory_user:
 
            # the path to the user class (this must point to InMemoryUser)
            path: @conga/framework-security:security/user/InMemoryUser
 
            # the algorithm you want to use
            algorithm: bcrypt
 
            # bcrypt salt-rounds config
            saltRounds: 50
 
            # application-wide pepper
            pepper: abc123!=!321CBA

You can have as many users with different roles and passwords as you wish, but they have to use the same encryption strategy, as they are all of the same InMemoryUser class.

After you set up your encryptor, you can generate an encrypted password with the following example:

conga security:encrypt --path @conga/framework-security:security/user/InMemoryUser --value admin-password

Then copy / paste this password under your user's password property, in your security configuration.

Bass Provider

The bass provider allows you to use any bass adapter as your persistence layer. Each collection (or database table) will have its own document (model), and so each separate user type you want to configure should have its own encryption strategy defined.

The bass provider requires that you specify the login and secret field on your document, so that the provider knows how to access them. Currently, methods are not supported, so provide the public property name so the provider can reference them correctly.

security:
 
    # these are examples, you can use any variation you want
 
    encryption:
    
        administrator:
            path: project-bundle:model/Administrator
            algorithm: bcrypt
            saltRounds: 50
            pepper: TYHJK4%^&238765rtghydjk     # application-wide pepper
        
        user:
            path: project-bundle:model/User
            algorithm: sha512
            pepper: KJHGTYU45678^%$aSFFAf!      # application-wide pepper
            fields:
                salt: encryptionSalt            # grab the salt from the User document so each user has a unique salt!
                secret: encryptionSecret        # grab the hmac secret from the User document so each user has a uniuqe secret!
    
    providers:
    
        admin_user_provider:
            bass:
                document: project-bundle:model/Administrator    # the document path
                login: username                                 # the login / username field in the document
                secret: password                                # the password field in the document
                change_password_logout: true                    # make sure users are logged out on password change
    
        website_user_provider:
            bass:
                document: project-bundle:model/User             # the document path
                login: email                                    # the login / username field in the document
                secret: password                                # the password field in the document
                change_password_logout: true                    # make sure users are logged out on password change

In the above configuration, you can see that we have configured two different documents, each with their own encryption strategy.

The storage engine / persistence layer can now be managed from within your bass configuration, for whichever adapters these documents are registered under (you might have more than one!).

The encrypted passwords live in the row matching the login / secret for each document, inside the database. For that reason, among so many others, it's important that your login / secret combination is unique for each user.

Chain Provider

The chain provider simply allows you to chain multiple providers together. During authentication, it will try each provider in the chain, one by one, in series, until it finds one that it can use, or until it has tried them all.

security:
 
    providers:
    
        # chain the adminstrator access providers together
        admin_chain: [admin_memory_provider, admin_user_provider]
        
        # chain the front-end access providers together
        frontend_chain: [front_memory_provider, website_user_provider, my_provider]
        
        # chain everything together (uses nested chains!)
        chain: [admin_chain, frontend_chain]

Custom Service

You can use your own service as your provider, if you wish. The only catch is that your service must be an instance of a class that inherits from @conga/framework-security:security/provider/AbstractProvider.

my-bundle:service/MyProviderService

const { AbstractProvider } = require('@conga/framework-security').Provider;
 
class MyProviderService extends AbstractProvider {
    /**
     * Our example injects the service container
     * @param {Container} container The service container
     **/
    constructor(container) {
        this.container = container;
    }

    /**
     * See if this provider supports a given resource
     * @param {AuthResource|*} resource The resource to check for
     * @returns {boolean}
     */
    supportsResource(resource) {
        // you must implement this method
    }
    
    /**
     * Get the resource that needs to be authenticated
     * @param {*} credentials The secret password / api key, etc.
     * @returns {Promise}
     */
    getResource(credentials) {
        // you must implement this method
    }
    
    /**
     * Refresh an existing resource
     * @param {AuthResource|*} resource
     * @returns {Promise}
     */
    refreshResource(resource) {
        // you must implement this method
    }
}
 
module.exports = MyProviderService;

services.yml

services:
 
    my_provider_service:
        constructor: my-bundle:service/MyProviderService
        arguments: ['@service_container']

config.yml

security:
 
    providers:
     
        my_provider: '@my_provider_service'
    

Custom Configurable Providers

If you want to provide your own configurable provider, like the in-memory provider, or the bass provider, you can do that. You will need to register your provider with a service tag, and provide a method that will be used to check if your provider matches the security configuration.

You might want to do something like this if you are building a bundle for others to use!

my-bundle:security/provider-config

You need to create a provider config class that extends AbstractProviderConfig, and tag it in your service definition, so that conga-security can register it.

const AbstractProviderConfig = require('conga-security').Provider.AbstractProviderConfig;
const MyProvider = require('./provider');
 
class MyProviderConfig extends AbstractProviderConfig {
 
    constructor(container) {
        // if you need it, you can inject the container into your provider configuration
        this.container = container;
    }
    
    // abstract method you are required to provide
    supportsConfig(config) {
 
        // 'bundle_provider' is what we are checking for to see if our provider should be used
        // you can check for ANYTHING that can be configured in your config
 
        return config.bundle_provider instanceof Object;
 
    }
    
    // abstract method you are required to provide
    useConfig(config) {
        return new MyProvider(config);
    }
 
}
 
module.exports = MyProviderConfig;

my-bundle:security/provider

You need to create a provider class that extends AbstractProvider. You don't need to register this anywhere, your provider config class will return it when useConfig is called.

const AbstractProvider = require('conga-security').Provider.AbstractProvider;
 
class MyProvider extends AbstractProvider {
 
    // abstract method you are required to provide
    getResource(credentials) {
 
        // this is where you would fetch your resource (user) from a persistence layer or API, etc.
        // return a promise that resolves an AuthResource type
        
        // this example simply returns an AuthUser, which extends AuthResource, with custom roles
 
        const { username, password } = credentials;
        return Promise.resolve(new AuthUser(username, password, ['ROLE_CUSTOM']));
    }
 
}

module.exports = MyProvider;

services.yml

You need to register your provider config with a service definition and tag it with security.firewall.provider.

services:
 
    bundle.provider:
        constructor: my-bundle:security/provider-config
        arguments: ["@service_container"]
        tags: 
            - { name: security.firewall.provider }

config.yml

Then you can use your provider by configuring it in your security section in app/config/config.yml.

security:
    
    firewall:
        
        my.firewall:
            route: ^/foo/bar
            authenticator: my.authenticator
            provider: my.provider
    
    providers:
        
        my.provider:
            bundle_provider:
                foo: asdfasdf
                bar: asdfasdf