Less talk, more code

The blog of Iskandar Soesman

Mengengembangkan Single Sign On System (SSO) menggunakan OAuth 2.0

OAuth merupakan sebuah protokol yang saat ini mulai banyak digunakan oleh penyedia layanan internet seperti Facebook, Yahoo, Twitter, Google dll. Protokol ini membantu sharing informasi dan sumberdaya pribadi seseorang dari suatu aplikasi ke aplikasi lainnya dengan cara yang lebih aman tanpa perlu user memberikan passwordnya. Untuk mendapatkan informasi ini, user terlebih dahulu harus memberikan izin kepada aplikasi pihak ketiga yang ingin mendapatkan informasi tentang dirinya.

OAuth 2.0 adalah pengembangan dari OAuth versi 1 yang dibuat dengan tujuan untuk menyederhanakan dan mempermudah proses alur pertukaran data. Walaupun demikian OAuth 2 sama sekali berbeda dengan OAuth versi 1 sehingga aplikasi yang baru mendukung versi 1 tidak lagi bisa digunakan. Provider yang masih menggunakan OAuth versi 1 diantaranya Yahoo, Google dan Twitter. Sedangkan yang sudah mendukung versi 2 adalah Facebook, Google (experimental) dan Github. Untuk selanjutnya pembahasan dalam artikel ini hanya menitikberatkan pada OAuth 2.0.

Dalam proses ini setidaknya terdapat dua aplikasi yang saling berinteraksi satu sama lain. Aplikasi yang menyediakan sumberdaya disebut OAuth Provider dan yang menerima disebut OAuth client.

Sebelum membahas lebih lanjut, mari kita lihat bagai mana protokol ini bekerja dengan menggunakan Facebook sebagai OAuth provider.

SSO umumnya digunakan pada suatu layanan yang memiliki banyak aplikasi yang masing-masing aplikasi ini berdiri sendiri. Sebagai contoh jika kita memberikan layanan blog dimana user bisa menggunakan layanan ini dengan cara memasukan username dan password, maka ketika kita memiliki layanan lain misalnya forum ada baiknya user yang ingin memanfaatkan layanan ini tidak perlu dimintakan password dan usernamenya lagi. Contoh dari layanan yang sudah menerapkan mekanisme SSO untuk semua layanannya adalah Google dan Yahoo. Silahkan buka dan login di http://www.google.com/. Setelah login silahkan buka tab baru di browser dan buka halaman http://goo.gl/. Sebagaimana Anda lihat, setelah berhasil login di halaman muka google.com kita juga bisa langsung menikmati layanan lainnya dari google tanpa perlu lagi memasukan username atau password.

Istilah pada OAuth

Client Id dan Client Secret

Untuk memanfaatkan resource dari aplikasi provider, aplikasi client harus memiliki client id dan client secret yang diberikan oleh aplikasi provider. Berikut adalah halaman untuk mendapatkan client id dan client secret untuk beberapa layanan OAuth Provider:

Facebook: https://www.facebook.com/developers/createapp.php

Google: http://code.google.com/apis/console

Github: http://github.com/account/applications/new

Redirect Uri

Ini adalah sebuah halaman yang dipersiapkan khusus oleh aplikasi client untuk memproses callback yang diberikan aplikasi provider. Pada halaman ini aplikasi client akan mengolah informasi yang diberikan oleh aplikasi provider berupa code unik yang harus digunakan untuk proses selanjutnya.

Code

Code adalah sebuah string unik yang diberikan aplikasi provider kepada aplikasi client pada halaman callback yang sebelumnya sudah didefenisikan pada parameter redirect uri. Code dibutuhkan untuk kemudian ditukarkan kembali ke aplikasi provider untuk mendapatkan access token.

Access Token

Access token adalah sebuah string penanda izin otorisasi yang dibutuhkan aplikasi cilent untuk memanfaatkan sumberdaya seorang user yang ada di aplikasi provider. Jika client sudah mendapatkan acces token dari provider maka pertukaran data sudah bisa dilakukan misalnya untuk mendapatkan nama user, foto, email, memposting artikel ke aplikasi provider dll.

Scope

Scope adalah opsi tambahan untuk mendapatkan informasi spsifik pada user yang umumnya secara default tidak langsung diberikan oleh aplikasi provider.

User Authentication

Pertama-tama user harus login dan melakukan proses otentifikasi di aplikasi provider. Aplikasi client harus me-redirect usernya ke sebuah halaman otentifikasi yang sudah ditentukan oleh aplikasi provider. Pada saat melakukan proses ini, aplikasi client harus menyertakan parameter client_id dan redirect_uri pada alamat url halaman otentifikasi. Contohnya seperti berikut:

https://www.appprovider.com/login_page?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL

Contoh halaman otentifikasi Facebook:

https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL

Contoh halaman otentifikasi Google:

https://accounts.google.com/o/OAuth 2/auth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL

Contoh halaman otentifikasi Github:

https://github.com/login/oauth/authorize?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL

Jika proses login berhasil, user kemudian akan dibawa kehalaman dialog dimana dia akan diberikan pilihan apakah akan mengizinkan resource-nya diakses oleh aplikasi client atau tidak. Jika user memberikan uzin, maka selanjutnya user akan diredirect ke halaman callback aplikasi client yang nilainya sudah diisikan pada parameter redirect_uri.

Proses konfirmasi ini hanya dilakukan sekali saja. Jika user sudah pernah memberikan izin maka akan langsung diarahkan ke halaman callback.

Jika aplikasi client membutuhkan informasi tambahan yang tidak langsung disediakan oleh aplikasi client, maka pada tahap ini parameter scope bisa ditambahakan. Setiap penyedia layanan OAuth provider memiliki daftarnya sendiri-sendiri. Sebagai contoh berikut contoh penggunaan scope pada halaman otentifikasi Facebook:

https://www.facebook.com/dialog/oauth?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_URL&scope=email,read_stream

Daftar pilihan scope Facebook bisa dilihat di halaman https://developers.facebook.com/docs/authentication/permissions

Contoh penggunaan scope pada Google

https://accounts.google.com/o/OAuth 2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_URL&scope=https://mail.google.com/mail/feed/atom/&response_type=code

Mendapatkan Access Token

Setelah user diredirect ke halaman callback aplikasi client, maka proses selanjutnya adalah memanfaatkan argument code yang disertakan pada alamat url. Contoh alamat url halaman callback yang di-redirect dari aplikasi provider:

http://www.appclient.com/callback_page?code=A_CODE_GENERATED_BY_PROVIDER

Proses selanjutnya adalah mengirimkan kembali nilai variabel code ke aplikasi provider melalui hattp request untuk ditukarkan dengan akses token. Jika berhasil API aplikasi provider akan memberikan response string yang berisi access token dan berapa lama masa berlakunya.

String access token yang sudah didapatkan harus disimpan untuk keperluan penggunaan resource user pada aplikasi provider.

Menggunakan Resource User

Sampai pada tahap ini aplikasi client sudah siap untuk memanfaatkan resource yang tersedia pada aplikasi provider. Sebagai contoh jika kita ingin mendapatkan nama dan informasi tentang user maka aplikasi client harus melakukan request ke apliaksi provider dengan alamat yang sudah ditentukan olehnya dengan menyertakan access token yang sudah didapatkan sebelumnya. Sebagai contoh jika ingin mendapatkan basic info dari Facebook alamat API yang harus diakses dengan method GET adalah:

https://graph.facebook.com/me?access_token=ACCESS_TOKEN

Contoh mendapatkan user info pada Gituhub:

https://github.com/api/v2/json/user/show?access_token=ACCESS_TOKEN

Contoh memposting artikel di Blogger:

POST http://www.blogger.com/feeds/blogID/posts/default?oauth_token=ACCESS_TOKEN
dengan isi body post:

<entry xmlns='http://www.w3.org/2005/Atom'>
  <title type='text'>Marriage!</title>
  <content type='xhtml'>
    <div xmlns="http://www.w3.org/1999/xhtml">
      <p>Mr. Darcy has <em>proposed marriage</em> to me!</p>
      <p>He is the last man on earth I would ever desire to marry.</p>
      <p>Whatever shall I do?</p>
    </div>
  </content>
  <category scheme="http://www.blogger.com/atom/ns#" term="marriage" />
  <category scheme="http://www.blogger.com/atom/ns#" term="Mr. Darcy" />
</entry>

Membuat Aplikasi Client

Berikut kita akan membuat contoh aplikasi client OAuth dengan menggunakan Facebook sebagai OAuth provider. Pastikan Anda telah memiliki client id dan client secret yang diberikan oleh Facebook. Silahkan mendaftar di halaman https://www.facebook.com/developers/createapp.php jika belum.

Pada contoh yang akan digunakan menggunakan framework Panada, namun bisa juga diimplementasikan pada framework lain atau bahkan PHP scratch. Sebagai catatan contoh yang digunakan di bawah ini ditulis sesederhana mungkin sehingga mengabaikan pakem MVC dengan tujuan untuk mempermudah penjelasan.

Pertama-tama kita membutuhkan dua buah file class PHP yaitu oauth2_client.php dan rest.php.

Khusus bagi Anda yang menggunakan Panada, class rest sudah tersedia pada core library sehingga tidak perlu lagi didownload.

Buat sebuah controller baru bernama auth bagi Anda yang menggunakan framework atau file baru bernama auth.php bagi yang memilih scratch.

Letakan oauth2_client.php dan rest.php pada folder library atau vendor atau apapun namanya di dalam framework yang Anda gunakan. Sedakan pada PHP scratch letakan satu level dengan file auth.php agar lebih mudah untuk melakukan include. Jika folder document root localhost Anda berada di:

/var/www/public_html/

Maka tambahkan satu folder beru bernama client, sehingga strukturnya menjadi:

/var/www/public_html/client/

Berikut adalah isi controller auth pada Panada:

<?php

class Controller_auth extends Panada {

    public function __construct(){

        parent::__construct();

        $this->rest = new Library_rest();
        $this->oauth2_client = new Library_oauth2_client();

        $this->oauth2_client->client_id = 'MY_CLIENT_ID';
        $this->oauth2_client->client_secret = 'MY_CLIENT_SECRET';
        $this->oauth2_client->redirect_uri = 'http://localhost/client/auth/callback';
    }

    public function index(){

        $provider_auth_uri = 'https://www.facebook.com/dialog/oauth';
        $more_args = array( 'scope' => 'email' );

        $this->oauth2_client->user_authentication( $provider_auth_uri, $more_args );
    }

    public function callback(){

        $code = $_GET['code'];
        $provider_token_uri = 'https://graph.facebook.com/oauth/access_token';

        $response = $this->oauth2_client->get_access_token( $provider_token_uri, $code );

        parse_str($response);

        $provider_uri = 'https://graph.facebook.com/me';

        $response = $this->oauth2_client->access_user_resources( $provider_uri, $access_token );

        $data = json_decode( $response );

        print_r($data);
    }
}

Berikut isi file auth.php pada PHP scretch:

<?php

require_once 'rest.php';
require_once 'oauth2_client.php';

$rest = new Library_rest();
$oauth2_client = new Library_oauth2_client();

$oauth2_client->client_id = 'MY_CLIENT_ID';
$oauth2_client->client_secret = 'MY_CLIENT_SECRET';
$oauth2_client->redirect_uri = 'http://localhost/client/auth.php?callback';

if( isset($_GET['login']) ) {

    $provider_auth_uri = 'https://www.facebook.com/dialog/oauth';
    $more_args = array( 'scope' => 'email' );

    $oauth2_client->user_authentication( $provider_auth_uri, $more_args );
}

if( isset($_GET['callback']) ) {

    $code = $_GET['code'];
    $provider_token_uri = 'https://graph.facebook.com/oauth/access_token';

    $response = $oauth2_client->get_access_token( $provider_token_uri, $code );

    parse_str($response);

    $provider_uri = 'https://graph.facebook.com/me';

    $response = $oauth2_client->access_user_resources( $provider_uri, $access_token );

    $data = json_decode( $response );

    print_r($data);
}

Sekarang akses alamat http://localhost/client/auth jika Anda menggunakan framework atau alamat http://localhost/client/auth.php?login jika PHP scratch.

Jika tidak ada masalah akan muncul dump array yang berisi data personal Anda yang ada di Facebook. Data inilah yang kemudian akan kita manfaatkan untuk meng-otentifikasi user pada aplikasi kita.

Data ini bisa kita gunakan untuk:

  1. Melakukan pengecekan ke dalam database apakah data user ini sudah pernah tersimpan sebelumnya. Field email bisa kita gunakan sebagai identifer unik. Jika tidak ada maka simpan datanya ke dalam database.
  2. Jika data sudah pernah tersimpan kita bisa langsung menyimpan nilai-nilainya ke dalam session dan memastikan user sudah terotentifikasi di aplikasi kita.

Berikut contoh kode implementasi data yang sudah didapat dan di-assign menjadi session pada framework Panada:

<?php

class Controller_auth extends Panada {

    public function __construct(){

        parent::__construct();

        $this->rest = new Library_rest();
        $this->oauth2_client = new Library_oauth2_client();
        $this->session = new Library_session(); 

        $this->oauth2_client->client_id = '126220237453638';
        $this->oauth2_client->client_secret = 'd363579077a9ef22c5bbd871736212f1';
        $this->oauth2_client->redirect_uri = 'http://panadaframework.com/auth/callback';
    }

    public function index(){

        $provider_auth_uri = 'https://www.facebook.com/dialog/oauth';

        $more_args = array( 'scope' => 'email' );

        $this->oauth2_client->user_authentication( $provider_auth_uri, $more_args );
    }

    public function callback(){

        $code = $_GET['code'];
        $provider_token_uri = 'https://graph.facebook.com/oauth/access_token';

        $response = $this->oauth2_client->get_access_token( $provider_token_uri, $code );

        parse_str($response);

        $provider_uri = 'https://graph.facebook.com/me';

        $response = $this->oauth2_client->access_user_resources( $provider_uri, $access_token );

        $data = json_decode( $response );

        $this->session->set(
            array(
                'is_login' => 1,
                'name' => $data->name,
                'id' => $data->id,
            )
        );

        $this->redirect('auth/restricted_page');
    }

    public function restricted_page(){

        if( ! $this->session->get('is_login') )
            $this->redirect('auth');

        echo 'Selamat datang <b>' . $this->session->get('name') . '</b> | <a href="'.$this->location('auth/signout').'">Sign Out</a>';
    }

    public function signout(){

        $this->session->session_clear_all();

        echo 'Anda kini telah logout. Klik <a href="'.$this->location('auth').'">Sign In</a> untuk kembali login.';
    }
}

Implementasi juga bisa diterapkan aplikasi non framework. Berikut isi file auth.php pada PHP scretch:

<?php
session_start();

require_once 'rest.php';
require_once 'oauth2_client.php';

$rest = new Library_rest();
$oauth2_client = new Library_oauth2_client();

$oauth2_client->client_id = '126220237453638';
$oauth2_client->client_secret = 'd363579077a9ef22c5bbd871736212f1';
$oauth2_client->redirect_uri = 'http://panadaframework.com/auth.php?callback';

if( isset($_GET['login']) ) {

    $provider_auth_uri = 'https://www.facebook.com/dialog/oauth';
    $more_args = array( 'scope' => 'email' );

    $oauth2_client->user_authentication( $provider_auth_uri, $more_args );
}

if( isset($_GET['callback']) ) {

    $code = $_GET['code'];
    $provider_token_uri = 'https://graph.facebook.com/oauth/access_token';

    $response = $oauth2_client->get_access_token( $provider_token_uri, $code );

    parse_str($response);

    $provider_uri = 'https://graph.facebook.com/me';

    $response = $oauth2_client->access_user_resources( $provider_uri, $access_token );

    $data = json_decode( $response );

    $_SESSION['is_login'] = 1;
    $_SESSION['name'] = $data->name;
    $_SESSION['id'] = $data->id;

    header('Location:auth.php?restricted_page');
    exit;
}

if( isset($_GET['restricted_page']) ) {

    if( ! $_SESSION['is_login'] )
        header('Location:auth.php?login');

    echo 'Selamat datang <b>' . $_SESSION['name'] . '</b> | <a href="auth.php?signout">Sign Out</a>';
}

if( isset($_GET['signout']) ) {

    session_unset();
    session_destroy();

    echo 'Anda kini telah logout. Klik <a href="auth.php?login">Sign In</a> untuk kembali login.';
}

Membuat Aplikasi Provider

Sampai pada tahap ini kita sudah berhasil membuat aplikasi OAuth client dan memanfaatkan datanya untuk otentifikasi pada aplikasi kita. Untuk mengembangakn sistem SSO pada layanan yang kita kelola sendiri, kita membutuhkan sebuah aplikasi provider. Aplikasi ini nantinya juga akan kita manfaatkan sebagai pusat pengelolaan data dari seluruh member yang terdaftar pada seluruh layanan. Kalau kita perhatikan beberapa penyedia layanan internet yang telah mengimplementasikan SSO mereka juga melakukan pengelolaan user secara terpusat. Sebagai contoh Google akan mengarahkan user yang akan login di layanan mereka ke alamat https://www.google.com/accounts/Login?continue=ALAMAT_LAYANAN_GOOGLE dan Yahoo mengarahkan ke alamat https://login.yahoo.com/config/login_verify2?.intl=us&.src=KODE_LAYANAN&.done=ALAMAT_LAYANAN_YAHOO.

Perlu diperhatikan, OAuth 2.0 mensyaratkan penggunaan SSL (https) protokol untuk penggunaan production OAuth provider. Untuk kemudahan, semua contoh aplikasi provider dalam tulisan ini hanya menggunakan http.

Pertama-tama siapkan sebuah folder baru di document root dengan nama provider sehingga struktrunya seperti berikut:

/var/www/public_html/provider/

Kita akan membuat satu file baru yang akan diletakan di dalam folder ini. Beri nama file tersebut user.php untuk non framework dan buat controller baru bernama user untuk yang menggunakan framework.

Untuk mengelola user kita membutuhkan sebuah database penyimpanan, misalnya kita beri nama SSO. Berikut ini struktur tabel-tabel dan data contoh yang diperlukan:

CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(32) NOT NULL,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
);

INSERT INTO `users` (`username`, `password`, `name`) VALUES
('user', 'ee11cbb19052e40b07aac0ca060c23ee', 'Budi');

CREATE TABLE `access_privileges` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` INT UNSIGNED NOT NULL,
`client_id` varchar(32) NOT NULL,
`code` VARCHAR(32) NOT NULL,
`access_token` VARCHAR(32) NOT NULL,
`redirect_uri` TEXT NOT NULL,
`expires` BIGINT UNSIGNED NOT NULL
);

CREATE TABLE `apps_clients` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`client_id` VARCHAR(32) NOT NULL,
`client_secret` VARCHAR(32) NOT NULL,
`logout_url` TEXT NOT NULL
);

INSERT INTO `apps_clients` (
`id`, `client_id`, `client_secret`, `logout_url`)
VALUES (NULL, 'c4ca4238a0b923820dcc509a6f75849b',
'4fc9b72a83a99a594d40b3971874a9be',
'http://localhost/client/auth.php?self_logout'
);

Setting konfigurasi framework yang Anda gunakan sesuai dengan environment development agar bisa terkoneksi dengan database server. Buat sebuah contorller baru bernama user atau file baru bernama user.php jika menggunakan PHP scretch. Berikut contoh isi controller-nya:

<?php

class Controller_user extends Panada {

    public function __construct(){

        parent::__construct();

        $this->db = new Library_db();
        $this->rest = new Library_rest();
        $this->request = new Library_request();
        $this->session = new Library_session(); 

    }

    public function index(){

    }

    public function signin(){

        if( ! $this->session->get('is_provider_login') ) {

            $error = null;
            $client_id = $_GET['client_id'];
            $redirect_uri = urldecode($_GET['redirect_uri']);
            $parsed = parse_url($redirect_uri);

            if( isset($parsed['query']) )
                parse_str($parsed['query'], $query_args);

            if( isset($_POST['submit']) ){

                if( $user = $this->db->get_row('users', array('username' => $_POST['username'], 'password' => md5($_POST['password'])) ) ) {

                    $query_args['code'] = rand();
                    $access_token = rand();
                    $expires = strtotime('next hour');
                    $parsed['host'] = rtrim($parsed['host'], '/').'/';
                    $redirect_to = $parsed['scheme'].'://'.$parsed['host'];

                    if( isset($parsed['path']) )
                        $redirect_to .= $parsed['path'];

                    $redirect_to .= '?'.http_build_query( $query_args );

                    if( isset($parsed['fragment']) )
                        $redirect_to .= '#'.$parsed['fragment'];

                    $this->db->delete('access_privileges',
                                      array(
                                        'client_id' => $client_id,
                                        'user_id' => $user->id
                                        )
                                    );

                    $this->db->insert('access_privileges',
                                      array(
                                        'client_id' => $client_id,
                                        'user_id' => $user->id,
                                        'code' => $query_args['code'],
                                        'access_token' => $access_token,
                                        'redirect_uri' => $redirect_uri,
                                        'expires' => $expires
                                    )
                                );

                    $this->session->set( array('is_provider_login' => $user->id, 'name' => $user->name) );

                    $this->redirect( $redirect_to );
                }

                $error = '<p>Username atau password salah.</a>';
            }

            echo $error;
            echo '<form action="" method="post">';
            echo 'Username: <input type="text" name="username" /><br />';
            echo 'Password: <input type="password" name="password" /><br />';
            echo '<input type="submit" name="submit" value="Sign In" /><br />';
            echo '</form>';
        }
        else {

            echo 'Hallo <b>'.$this->session->get('name').'</b> Saat ini Anda sudah login di OAuth Provider | <a href="signout">Sign Out</a>';
        }
    }

    public function signout(){

        $this->session->session_clear_all();

        echo 'Anda kini telah logout dari OAuth Provider. Silahkan login menggunakan OAuth Client Anda.';
    }

    public function access_token(){

        $code = $this->request->get('code');
        $client_id = $this->request->get('client_id');
        $client_secret = $this->request->get('client_secret');
        $redirect_uri = $this->request->get('redirect_uri');

        $data = (array) $this->db->row("SELECT
                                    access_privileges.access_token, access_privileges.expires
                                FROM
                                    access_privileges, apps_clients
                                WHERE
                                    apps_clients.client_id = access_privileges.client_id AND
                                    apps_clients.client_id = '$client_id' AND
                                    apps_clients.client_secreet = '$client_secret' AND
                                    access_privileges.redirect_uri = '$redirect_uri' AND
                                    access_privileges.code = '$code'

                            ");

        if( ! $data )
            $data['error'] = 'Argument query yang Anda berikan tidak cocok.';

        echo urldecode(http_build_query($data));
    }

    public function get_data(){

        $access_token = $this->request->get('access_token');

        $data = $this->db->row("
                            SELECT
                                users.id, users.username, users.name
                            FROM
                                access_privileges, users
                            WHERE
                                users.id = access_privileges.user_id AND
                                access_privileges.access_token = '$access_token'
                        ");

        if( ! $data )
            $data['error'] = 'Argument query yang Anda berikan tidak cocok.';

        echo $this->rest->wrap_response_output($data, 'javascript');

    }
}

Dan berikut isi file user.php jika menggunakan PHP scretch:

<?php
session_start();

$link = mysql_connect('localhost', 'root', '');
$db_selected = mysql_select_db('komps', $link);

if( isset($_GET['signin']) ) {

    if( ! isset($_SESSION['is_provider_login']) ){

        $error = null;
        $client_id = $_GET['client_id'];
        $redirect_uri = urldecode($_GET['redirect_uri']);
        $parsed = parse_url($redirect_uri);

        if( isset($parsed['query']) )
            parse_str($parsed['query'], $query_args);

        if( isset($_POST['submit']) ){

            if( $user = mysql_fetch_object( mysql_query("SELECT * FROM users WHERE username = '".$_POST['username']."' AND password = '".md5($_POST['password'])."'")) ) {

                $query_args['code'] = rand();
                $access_token = rand();
                $expires = strtotime('next hour');
                $parsed['host'] = rtrim($parsed['host'], '/').'/';
                $redirect_to = $parsed['scheme'].'://'.$parsed['host'];

                if( isset($parsed['path']) )
                    $redirect_to .= $parsed['path'];

                $redirect_to .= '?'.http_build_query( $query_args );

                if( isset($parsed['fragment']) )
                    $redirect_to .= '#'.$parsed['fragment'];

                mysql_query("DELETE FROM access_privileges WHERE client_id = '$client_id' AND user_id = $user->id");
                mysql_query("INSERT INTO access_privileges (client_id, user_id, code, access_token, redirect_uri, expires) VALUES ('$client_id', '$user->id', '".$query_args['code']."', '$access_token', '$redirect_uri', '$expires')");

                $_SESSION['is_provider_login'] = $user->id;
                $_SESSION['name'] = $user->name;

                header('Location:'.$redirect_to);
            }

            $error = '<p>Username atau password salah.</a>';
        }

        echo $error;
        echo '<form action="" method="post">';
        echo 'Username: <input type="text" name="username" /><br />';
        echo 'Password: <input type="password" name="password" /><br />';
        echo '<input type="submit" name="submit" value="Sign In" /><br />';
        echo '</form>';
    }
    else {

        echo 'Hallo <b>'.$_SESSION['name'].'</b> Saat ini Anda sudah login di OAuth Provider | <a href="?signout">Sign Out</a>';
    }
}

if( isset($_GET['signout']) ) {

    session_destroy();

    echo 'Anda kini telah logout dari OAuth Provider. Silahkan login menggunakan OAuth Client Anda.';
}

if( isset($_GET['access_token']) && ! isset($_GET['get_data']) ){

    $code = $_GET['code'];
    $client_id = $_GET['client_id'];
    $client_secret = $_GET['client_secret'];
    $redirect_uri = $_GET['redirect_uri'];

    $data = mysql_fetch_array(
                            mysql_query("
                                SELECT
                                    access_privileges.access_token, access_privileges.expires
                                FROM
                                    access_privileges, apps_clients
                                WHERE
                                    apps_clients.client_id = access_privileges.client_id AND
                                    apps_clients.client_id = '$client_id' AND
                                    apps_clients.client_secreet = '$client_secret' AND
                                    access_privileges.redirect_uri = '$redirect_uri' AND
                                    access_privileges.code = '$code'

                            "), MYSQL_ASSOC);

    if( ! $data )
        $data['error'] = 'Argument query yang Anda berikan tidak cocok.';

    echo urldecode(http_build_query($data));
}

if( isset($_GET['get_data']) ){

    $access_token = $_GET['access_token'];

    $data = mysql_fetch_object(
                        mysql_query("
                            SELECT
                                users.id, users.username, users.name
                            FROM
                                access_privileges, users
                            WHERE
                                users.id = access_privileges.user_id AND
                                access_privileges.access_token = '$access_token'
                        ")
                    );

    if( ! $data )
        $data['error'] = 'Argument query yang Anda berikan tidak cocok.';

    echo json_encode($data);
}

Sekarang kita coba dengan terlebih dahulu memodifikasi aplikasi client yang sebelumnya sudah kita buat. Sesuaikan alamat url provider seperti berikut ini:

<?php
session_start();

require_once 'rest.php';
require_once 'oauth2_client.php';

$rest = new Library_rest();
$oauth2_client = new Library_oauth2_client();

$oauth2_client->client_id = 'c4ca4238a0b923820dcc509a6f75849b';
$oauth2_client->client_secret = '4fc9b72a83a99a594d40b3971874a9be';
$oauth2_client->redirect_uri = 'http://localhost/client/auth.php?callback';

if( isset($_GET['login']) ) {

    $provider_auth_uri = 'http://localhost/provider/user.php';
    $more_args = array( 'scope' => 'email', 'signin' => 'true' );

    $oauth2_client->user_authentication( $provider_auth_uri, $more_args );
}

if( isset($_GET['callback']) ) {

    $code = $_GET['code'];
    $provider_token_uri = 'http://localhost/provider/user.php';

    $response = $oauth2_client->get_access_token( $provider_token_uri, $code, 'GET', array('access_token' => 'true') );

    parse_str($response);

    $provider_uri = 'http://localhost/provider/user.php';

    $response = $oauth2_client->access_user_resources( $provider_uri, $access_token, 'GET', array('get_data' => 'true') );

    $data = json_decode( $response );

    $_SESSION['is_login'] = 1;
    $_SESSION['name'] = $data->name;
    $_SESSION['id'] = $data->id;

    header('Location:_auth.php?restricted_page');
    exit;
}

if( isset($_GET['restricted_page']) ) {

    if( ! $_SESSION['is_login'] )
        header('Location:auth.php?login');

    echo 'Selamat datang <b>' . $_SESSION['name'] . '</b> | <a href="auth.php?signout">Sign Out</a>';
}

if( isset($_GET['signout']) ) {

    session_unset();
    session_destroy();

    echo 'Anda kini telah logout. Klik <a href="auth.php?login">Sign In</a> untuk kembali login.';
}

if ( isset($_GET['self_logout']) ){

    session_unset();
    session_destroy();
}

Sekarang kembali akses alamat http://localhost/client/auth.php?login dari browser Anda. Jika berhasil di browser akan menampilkan pesan "selamat datang budi".

Crossdomain Authentication

Untuk pengujian lebih lanjut, kita harus pastikan bahwa mekanisme SSO yang sudah kita buat bisa berjalan pada domain yang berbeda-beda (crossdomain authentication). Untuk itu silahkan buat virtual host baru di komputer Anda dengan nama domain selain localhost, misalnya myhost. Setelah berhasil pastikan Anda menambahakan nilai:

127.0.0.1 myhost

di dalam folder:

/etc/hosts

Konfigurasi ini bertujuan ketika diakses dari browser dengan alamat http://myhost/ akan mengarahkan ke komputer yang sedang kita gunakan.

Setaleh dipastikan berhasil, letakkan aplikasi provider yang sudah kita buat ke folder virtual host myhost sehingga alamat url aplikasi provider-nya menjadi

http://myhost/provider/

Sesuaikan kembali parameter yang ada pada code aplikasi client dan silahkan coba kembali.

Single Sign On/Out

Kini kedua aplikasi sudah berjalan sesuai harapan. Kita bisa melakukan otentifikasi user dari satu aplikasi ke aplikasi lainnya tanpa perlu lagi mengisikan username atau password. Namun sampai tahap ini prosesnya masih berjalan manual. User harus mengklik link terlebih dahulu untuk bisa terotentifikasi.

Langkah selanjutnya adalah kita akan membuat user benar-benar merasakan Single Sign On. Sebagi contoh user login di aplikasi dengan domain myshost, kemudian dia membuka halaman baru dengan domain localhost, maka kita ingin dia juga sudah terotentifikasi. Hal yang sama juga berlaku ketika user logout. Logout di satu aplikasi maka ter-logout juga di aplikasi lainnya.

Untuk melakukan hal ini kita akan menggunakan bantuan javascript sebagai login redirector, dan tag html img sebagai pen-triger logout.

Javascript Login Redirector

Buat sebuah blok baru di dalam file user.php atau controller controller user. Tugas blok ini adalah mendeteksi apakah seorang user sudah login pada aplikasi provider atau belum. Jika sudah login maka bagian ini akan mencetak output javascript yang me-redirect ke halaman login provider. Berikut contoh code-nya:

<?php
session_start();

header("Content-type: text/javascript");

if( isset($_SESSION['is_provider_login']) ){
    echo 'top.location.href=" http://myhost/provider/user.php?client_id=c4ca4238a0b923820dcc509a6f75849b&redirect_uri=http://localhost/client/auth.php?callback&signin=true";';
}

File ini akan dianggap file javascript oleh browser, sehingga untuk meload-nya kita gunakan tag <script> seperti berikut:

<script type="text/javascript" src="http://myhost/provider/user.php?redirector"></script>

Letakkan script ini disetiap halaman aplikasi clent yang ingin login secara otomatis. Gunakan script ini hanya dalam kondisi user belum login di aplikasi client.

Img Logout Triger

Terakhir untuk 'memakasa' aplikasi client melakukan logout tambahkan code berikut di bagian logout aplikasi provider:

if( isset($_GET['signout']) ) {

    session_destroy();

    $result = mysql_query( "SELECT logout_url FROM apps_clients" );

    while ($row = mysql_fetch_object($result)) {
        echo '<img src="'.$row->logout_url.'" width="0" height="0" alt="" />';
    }
}

Sebagaimana kita lihat untuk melakukan logout client aplikasi provider membutuhkan alamat url 'self logout' dari client. Nilai ini harus diisikan ketika akan menambahkan aplikasi client baru.

Untuk memusatkan proses logout, semua client harus melakukan logout di aplikasi provider dengan cara memberikan link logout ke halaman logout provider.

<a href="http://myhost/provider/user.php?signout&redirect_to=http://localhost/client/auth.php?signout">Logout</a>

Untuk melihat demo dari artikel ini, buka halaman http://panadaframework.com/samples/provider/user dan halaman http://kandar.info/demo/oauth2/client/auth.php pada tab yang berbeda. Login di halaman Provider, kemudian buka tab halaman Client dan refrash.

Lakukan logout di salah satu halaman dan lakukan refresh di halaman yang lain untuk memastikan sudah ter-logout.