Authenticate Angular js module for Apigility
Asked Answered
M

3

21

I created an API using Apigility. I'm trying to set authentication system for a front end app that I'm currently building using the API.

But all angular authentication modules that I have used for this authentication system were not matching with Apigility oAuth 2 implementation :

  1. https://github.com/lynndylanhurley/ng-token-auth The problem with that module is, it doesn't allow CORS. However it allow to send a CORS-Request using a proxy on the server where angular code is located, which I have written in PHP using Guzzle. But with proxy ng-token-auth send a request twice succeed even if all authenticate data are false.

  2. https://github.com/sahat/satellizer This module need implementation of JWT but in the Apigility Authentication section I have not seen any documentation on it.

I need a help to finalize my project.

Misplace answered 19/2, 2015 at 1:41 Comment(3)
@JerinKAlexander, do you need to integrate with 3rd party authenticators (Google, FB, etc)? Do you require OAuth specifically, or do you require a way to authenticate users?Stevenstevena
@JerinKAlexander, You can use ng-token-auth to resolve achieve your goal. After some readings of ng-auth-token's source code. I have get it work with Apigility. If you are interested by my code I can give a complete answer on how I have realized it.Misplace
you should definitely post your solutionStevenstevena
M
5

I will try to give a complete method of how I have made ng-token-auth work with ZF2. Primarily, ng-token-auth works fine with ruby module. So to make it work with ZF2 :

Resolve the CORS problem with these lines of code :

//HttpProvider
$httpProvider.defaults.useXDomain = true;
$httpProvider.defaults.headers.common['Access-Control-Request-Method'] = "POST, GET, PUT, DELETE";
$httpProvider.defaults.headers.common['Origin'] = "http://xxxxxxxxxxxxxxx";
$httpProvider.defaults.headers.common['Accept'] = "application/json";
$httpProvider.defaults.headers.common['Content-Type'] = "application/json; text/html";
delete $httpProvider.defaults.headers.common['X-Requested-With'];

Resolve the problem of CORS on ZF2 using ZFCORS as pointed in @josilber and @sven-lauterbach answer

Format response sent by ZF2 to make it work with ng-token-auth using these lines of codes

$http.defaults.transformResponse = function(value, headerGetters){
    var response_header = headerGetters(),
    response_data   = JsonHelper.IsJsonString(value) ? JSON.parse(value) : value;
    if(response_data){
        if(response_data.access_token)
            response_header['access_token']  = response_data.access_token;
        if(response_data.expires_in){
            var now = new Date().getTime();
            response_header['expires_in']    = now + ( parseInt(response_data.expires_in, 10) * 1000 );
        } 
        if(response_data.token_type)
            response_header['token_type']    = response_data.token_type;
        if(response_data.refresh_token)
            response_header['refresh_token'] = response_data.refresh_token;
        if(response_data.scope)
            response_header['scope']         = response_data.scope;
        return response_data;
    }
};

May be this is not the best way to transform response in AngularJS but it resolves the problem of formatting OAuth2 response which works with ng-token-auth

Finally, to send request to the server using auth token and refresh the token automatically, it was necessary to change some behavior of ng-token-auth. I have used decorate pattern on AngularJS to solve this issue with these snippets of code :

In app.js

//Change behavior of oauth2 module 
$provide.decorator("$auth", function($delegate, ApiAuthService){
    return ApiAuthService($delegate);
}); 

Where ApiAuthService is a factory defined by this snippet of code :

AuthProviderService.factory('ApiAuthService', ['MeService', function( MeService ){
    return function($delegate){
        return {
            initialize: function(){ return $delegate.initialize(); },
            apiUrl: function(configName){ },
            retrieveData: function(key){ return $delegate.retrieveData(key); },
            getConfig: function(name){ return $delegate.getConfig(name); },
            getExpiry: function(){  return $delegate.getExpiry(); },
            setAuthHeaders: function(h){ return $delegate.setAuthHeaders(h); },
            /*persistData: function(key, val, configName){ return $delegate.persistData(key, val, configName); },
            retrieveData: function(key){ return $delegate.retrieveData(key); },*/
            rejectDfd: function(reason){ $delegate.rejectDfd(reason); },
            invalidateTokens: function(){ return $delegate.invalidateTokens(); },
            submitLogin: function(params, opts){ return $delegate.submitLogin(params, opts); },
            validateUser: function(opts){  
                result = $delegate.validateUser(opts);
                return result;
            },
            deleteData: function(key){  
                return $delegate.deleteData(key);
            }
        };
    };
}]).config(['$httpProvider', function($httpProvider) {

    $httpProvider.interceptors.push([
         '$injector', function($injector) {
           return {
             request: function(req) {
               $injector.invoke([
                 '$http', '$auth', function($http, $auth) {
                   var key, 
                       _ref, 
                       _results = [];
                   if (req.url.match($auth.apiUrl())) {
                     _ref = $auth.retrieveData('auth_headers');
                     //Inject value into body of request 
                     for (key in _ref) {
                         //Set Authorization request header.
                         if(key.match('access_token')){
                             if(req.headers){
                                 req.headers['Authorization'] = 'Bearer ' + _ref[key]; 
                             }else{
                                 req.headers = {'Authorization': 'Bearer ' + _ref[key]};
                             }
                         }
                         if(req.headers[key]){
                             delete req.headers[key];
                         }
                     }
                     return _results;
                   }
                 }
               ]);
               return req;
             }
           };
         }
       ]);
}]);

Lastly my configuration of ng-token-auth was :

//OAuth2 Module configs
$authProvider.configure([ {
    "default": {
        apiUrl:                  API_URL,
        tokenValidationPath:     '/me',
        signOutUrl:              '/oauth',
        emailRegistrationPath:   '/oauth',
        accountUpdatePath:       '/oauth',
        accountDeletePath:       '/oauth',
        confirmationSuccessUrl:  window.location.href,
        passwordResetPath:       '/oauth',
        passwordUpdatePath:      '/oauth',
        passwordResetSuccessUrl: window.location.href,
        emailSignInPath:         '/oauth',
        forceHardRedirect: true,
        storage:                 'localStorage',
        proxyIf:                 function() { return false; },
        proxyUrl:                'proxy',
        authProviderPaths: {
            github:   '/auth/github',
            facebook: '/auth/facebook',
            google:   '/auth/google'
        },
        tokenFormat: {
            "access_token" : "{{ token }}",
            "token_type"   : "Bearer",
            "refresh_token": "{{ clientId }}",
            "expires_in"   : "{{ expiry }}",
            "scope"        : "{{ uid }}"
        },
        parseExpiry: function(headers) {
            var expires_in = parseInt(headers['expires_in'], 10) || null;
                return expires_in;
            },
            handleLoginResponse: function(response) {
                //Patch for persistant data as library retreive auth data from header.
                return response;
            },
            handleAccountResponse: function(response) {
                return response;
            },
            handleTokenValidationResponse: function(response) {
                return response;
            }
        }
} ]);

@JerinKAlexander I hope these steps will help you to find your way to solve your question in a better way than what I have done.

Misplace answered 25/6, 2015 at 0:36 Comment(0)
T
2

You can actually get satellizer to work with Apigility using a rather simple but neat workaround. Take a look here :

http://adam.lundrigan.ca/2014/11/06/using-oauth2-jwt-with-apigility/

and here:

https://github.com/adamlundrigan/LdcOAuth2CryptoToken/blob/master/src/Factory/CryptoTokenServerFactory.php

Apigility defines service factories for all it's internal services. The basic idea here is to simply define a service manager delegator factory which injects the necessary configuration.

<?php  
namespace LdcOAuth2CryptoToken\Factory;

use Zend\ServiceManager\DelegatorFactoryInterface;  
use Zend\ServiceManager\ServiceLocatorInterface;
class CryptoTokenServerFactory implements DelegatorFactoryInterface  
{
    public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback)
    {
        $server = call_user_func($callback);

        // do your thing to $server here

        return $server;
    }
}

All thanks to Adam Lundrigan :)

Thorner answered 18/6, 2015 at 10:17 Comment(3)
Jester, the link addresses the issue of crypto tokens which Apigility does not support. But you have not explained how to request tokens from Apigility end points, nor how to configure Apigility to create / send OAuth tokens, require / accept OAuth tokens.Stevenstevena
@DaveAlperovich Sorry, for a moment, I thought the core problem he is trying to address is that the angular authenticate modules not matching Apigility OAuth2. Plus configuring apigility OAuth2 is a very straightforward task, well documented in the Apigility docs (apigility.org/documentation/auth/authentication-oauth2)Thorner
To be fair, the OP was light on details and the investor has not added any details. My suspicion is that more is needed. But I'm only guessing without feedback. You may turn out to be right.Stevenstevena
D
1

You want to use Apigility as backend. You have a HTML App which runs on a different domain and this HTML App should call the Apigility backend with OAuth Authentication? If this is what you're trying to accomplish you have to setup Apigility to support CORS calls, take a look at https://apigility.org/documentation/recipes/allowing-request-from-other-domains

They use the "ZfrCors" module:

They use the following sample:

return array(
'zfr_cors' => array(
     /**
      * Set the list of allowed origins domain with protocol.
      */
     'allowed_origins' => array('http://www.sexywidgets.com'),

     /**
      * Set the list of HTTP verbs.
      */
     'allowed_methods' => array('GET', 'OPTIONS'),

     /**
      * Set the list of headers. This is returned in the preflight request to indicate
      * which HTTP headers can be used when making the actual request
      */
     'allowed_headers' => array('Authorization', 'Content-Type'),

     /**
      * Set the max age of the preflight request in seconds. A non-zero max age means
      * that the preflight will be cached during this amount of time
      */
     // 'max_age' => 120,

     /**
      * Set the list of exposed headers. This is a whitelist that authorize the browser
      * to access to some headers using the getResponseHeader() JavaScript method. Please
      * note that this feature is buggy and some browsers do not implement it correctly
      */
     // 'exposed_headers' => array(),

     /**
      * Standard CORS requests do not send or set any cookies by default. For this to work,
      * the client must set the XMLHttpRequest's "withCredentials" property to "true". For
      * this to work, you must set this option to true so that the server can serve
      * the proper response header.
      */
     // 'allowed_credentials' => false,
),
);

All you have to to is to set the 'allowed_origins' option to the domain of your HTML App.

For the OAuth part you can get more information here: https://apigility.org/documentation/auth/authentication-oauth2

You should take a closer look at the "Browser-based applications" section, because you use an HTML app to access your apigility backend. With the information provided in this post you can use https://github.com/sahat/satellizer

If you need more informations let me know.

Disaccredit answered 19/6, 2015 at 13:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.