Back-End

9 dez, 2016

Dominando OAuth 2.0

Publicidade

OAuth 2.0 é o padrão de fato para autenticar usuários com sites de terceiros. Se você quiser acessar os dados de um usuário no Google ou Facebook, por exemplo, OAuth 2.0 é o que você precisa. Mas vamos encarar um fato: OAuth 2.0 não é fácil e, para piorar as coisas, parece que todos têm uma implementação ligeiramente diferente, tornando a interoperabilidade um pesadelo. Felizmente, a PHP League of Extraordinary Packages lançou a biblioteca league/oauth2-client. Visando à simplicidade e facilidade de uso, a league/oauth2-client fornece uma interface comum para acessar muitos provedores OAuth 2.0.

OAuth resolve um problema específico: minimiza a exposição a credenciais. Ele consegue isso através de subvenções de autorização. Você concede a um website acesso às informações de sua conta de outro site. O site do beneficiário, então, usa credenciais temporárias chamadas tokens de acesso para acessar suas Informações do website concedente, geralmente através da API do concedente. Dessa forma, você usa somente seu nome de usuário e senha para autenticar com o serviço onde seus dados vivem, e não em outro lugar.

Vamos entrar de cabeça nisso!

Mas isso tudo soa confuso e prolixo. Para entender OAuth, é melhor vê-lo em ação e usaremos o Instagram para nosso exemplo, já que o Instagram é um provedor OAuth 2.0.

Queremos criar um site que puxe as fotos de um usuário do Instagram e mostre-as para o usuário em nosso website. Para fazer isso, o usuário tem que conceder permissão para nos permitir solicitar suas fotos do Instagram, mas eles não precisam nos dar seu nome de usuário e senha. Essas são credenciais secretas e sagradas, conhecidas apenas pelos usuários e pelo Instagram. Em vez disso, usaremos o OAuth 2.0 para solicitar permissão para acessar as fotos, mantendo as credenciais do usuário seguras.

Para criar um exemplo de aplicação rápida que ilustre os conceitos do OAuth 2.0, usaremos o Laravel Framework[1] com a biblioteca league/oauth2-client[2]. No entanto, a biblioteca league/oauth2-client pode ser usada com qualquer framework ou projeto autônomo.

Se você tem o Composer instalado, ótimo! Se não, vá para https://getcomposer.org e leia a seção “Getting Started” para descobrir como instalá-lo no seu sistema. Depois de ter o Composer instalado, você estará pronto para completar o resto deste exercício.

Abra o aplicativo do terminal e execute o seguinte comando para usar o Composer para criar um novo projeto Laravel. Este comando criará um diretório chamado oauth2-example.

composer create-project --prefer-dist laravel/laravel oauth2-example

No diretório oauth2-example, queremos executar alguns comandos para configurar nossos novos aplicativos, então use cd para alterar o diretório para oauth2-example. Em seguida, configure o scaffolding do Laravel para a autenticação com o seguinte comando:

php artisan make:auth

Não é relevante, neste momento, compreender tudo o que esse comando faz; em poucas palavras, ele configura todas as rotas e visualizações de que o Laravel precisa para fazer o trabalho de autenticação.

Em seguida, executaremos alguns comandos para configurar uma conexão de banco de dados simples usando o SQLite.

sed -i 's@DB_CONNECTION=mysql@DB_CONNECTION=sqlite@' .env
sed -i "s@DB_DATABASE=homestead@DB_DATABASE="$PWD"/database/database.sqlite@" .env
touch database/database.sqlite
php artisan migrate

Os dois primeiros comandos atualizam o arquivo .env que o comando create-project do Composer criou para nós. Eles dizem ao Laravel para usar o SQLite como banco de dados. O próximo comando cria um arquivo de banco de dados SQLite vazio, e o último comando executa migrações do Laravel para configurar a autenticação.

Se o seu sistema não tiver os programas sed ou touch, ou se estes comandos não funcionam, você pode abrir o arquivo .env em seu editor de texto favorito e fazer essas alterações manualmente. Você também pode usar o seu editor de texto para criar um banco de dados SQLite vazio com database/database.sqlite.

Agora, estamos prontos para executar o servidor web PHP built-in e podemos fazê-lo com o seguinte comando:

php artisan serve

Se tudo correu bem, quando você vai para http://localhost: 8000 no seu navegador, você deve ver uma página semelhante à Figura 1.

1Figura 1: Página de destino do aplicativo Laravel

Preparando-se para OAuth

Agora, use Ctrl + C para parar de executar o servidor web e vamos começar a trabalhar em nossa integração OAuth 2.0 com o Instagram. Eu criei um pacote para Laravel para tornar isso mais fácil. Vamos fazer isso com Composer.

composer require ramsey/laravel-oauth2-instagram

Abra o config/app.php em seu editor favorito e modifique os arrays providors e aliases com os valores mostrados na Listagem 1.

Listagem 1. Adicione o provedor de serviços e o facade alias

<?php
return [
    /* ... */

    'providers' => [
        /* ... */

        Ramsey\Laravel\OAuth2\Instagram\InstagramServiceProvider::class,
    ],

    'aliases' => [
        /* ... */

        'Instagram' => Ramsey\Laravel\OAuth2\Instagram\Facades\Instagram::class,
    ],
];

Nosso provedor de serviços do Instagram OAuth 2.0 está agora configurado para uso dentro da Laravel, e podemos executar o comando artisan para publicá-lo, o qual copia em torno de alguns arquivos de configuração.

php artisan vendor:publish

Agora é hora de configurar duas contas: uma em nosso aplicativo Laravel e outra no Instagram. Com o servidor web PHP built-in executando o nosso aplicativo (php artisan serve), vá para http://localhost: 8000/register e registre-se em uma conta.

Em seguida, vá para https://instagram.com/ para criar uma conta no Instagram (se você não tiver uma) e depois vá para https://instagram.com/developer/clients/register/ para se inscrever como desenvolvedor e registrar um cliente Instagram. Você pode querer se familiarizar com a Instagram Developer Documentation[3] ou tê-la à mão para referência.

Usaremos essas credenciais de cliente em nosso aplicativo Laravel. Ao registrar um cliente, sinta-se livre para usar os valores que você desejar, mas uma das Valid redirect URIs devem ser o valor:

http://localhost:8000/instagram

Usaremos essa URL em nosso aplicativo Laravel.

Depois de registrar um cliente Instagram, vamos configurar nosso aplicativo Laravel com ID do cliente e segredo do cliente fornecidos pelo Instagram. Abra o arquivo .env em um editor de texto e adicione os seguintes valores, trocando os Xs pelos valores do cliente Instagram.

INSTAGRAM_CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
INSTAGRAM_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
INSTAGRAM_REDIRECT_URI=http://localhost:8000/instagram

Integrando com Instagram

Até agora, foi tudo uma configuração preliminar, levando-nos ao ponto em que podemos começar integrando com o Instagram como um provedor OAuth 2.0. Cada provedor OAuth 2.0 tem ajustes de conta e etapas de configuração necessárias para fornecer ID de cliente única e valores secretos para qualquer aplicativo de cliente que queira integrar com o provedor.

Agora que temos as credenciais de nossos clientes, estamos prontos para começar a escrever o código. A Listagem 2 mostra nosso HomeController completo.

A Listagem 3 mostra a nossa home view modificada, mas vamos passar por cada conceito principal para explicar o que está acontecendo.

Também precisamos adicionar as seguintes rotas ao routes/web.php para ativar as novas rotas no HomeController:

Route::get('/instagram', 'HomeController@instagram');
Route::get('/forget-instagram', 'HomeController@forgetInstagram');

Pedido de autorização

O primeiro conceito principal do OAuth 2.0 que precisamos implementar é o pedido de autorização. Fazemos isso gerando uma URL de solicitação de autorização e também redirecionando um usuário para ela ou pedindo-lhe para que clique em um link ou um botão. A biblioteca do cliente league/oauth2-instagram[4] ajuda com o background lógico para gerar essa URL.

$authUrl = Instagram::authorize([], $redirectionHandler);

Observe como passamos um callback $redirectionHandler para Instagram::authorize(). Isso não é necessário, mas nos ajuda a definir o parâmetro de state na seção do usuário. Esse valor de estado está incluído na URL da solicitação de autorização e quando o provedor nos redirecionar, verificaremos o parâmetro state que enviam em contrapartida àquele armazenado na seção. Se eles não combinam, então sabemos que algo deu errado e que não devemos confiar na resposta do provedor.

$redirectionHandler = function ($url, $provider) use ($request) {
    $request->session()->put(
        'instagramState',
        $provider->getState()
    );

    return $url;
};

Quando o usuário é redirecionado ou clica em um link para a URL de solicitação de autorização do Instagram, ele irá se deparar com uma página semelhante à mostrada na Figura 2.

2Figura 2: Pedido de autorização do Instagram

No aplicativo de exemplo que configuramos, podemos ver como isso funciona indo para http://localhost:8000/home e clicando no link Click here to authorize with Instagram .

Redirecionando o endpoint

Depois que o usuário fornecer sua autorização para o aplicativo do nosso cliente, o Instagram o redirecionará para a URL de redirecionamento que definimos (http://localhost:8000/instagram), incluindo como parâmetros de consulta string um código de autorização e o mesmo estado que enviamos no pedido de autorização. Observe como verificamos o estado recebido em contrapartida ao armazenado na seção para garantir que eles correspondam entre si (veja o método instagram() na Listagem 2, linhas 65-69).

A próxima coisa que o redirecionamento do endpoint faz é trocar o código de autorização por um token de acesso.

$token = Instagram::getAccessToken('authorization_code', [
    'code' => $request->code,
]);

$request->session()->put('instagramToken', $token);

O método getAccessToken() no provedor league/oauth2-client faz uma solicitação para o Instagram, enviando o código de autorização e as credenciais do nosso cliente. O Instagram, então, envia de volta um token de acesso.

O token de acesso pode ser armazenado e reutilizado, de modo que um novo token não precise ser solicitado cada vez que um usuário deseja acessar os seus dados. Neste exemplo, estamos armazenando o token na seção do usuário, mas também podemos armazená-lo em um banco de dados ou em outro repositório de armazenamento.

Expirando e atualizando tokens

A maioria dos provedores inclui informações de expiração com o token de acesso, e muitos incluem também uma atualização de token. A biblioteca league/oauth2-client fornece funcionalidade para determinar se um token de acesso expirou e para que seja atualizado.

if ($token->hasExpired() && $token->getRefreshToken()) {
    $newToken = $provider->getAccessToken('refresh_token', [
        'refresh_token' => $token->getRefreshToken(),
    ]);
}

O Instagram não fornece informações de expiração de token de acesso ou tokens de atualização. Portanto, não precisamos usar essa funcionalidade em nosso aplicativo.

Usando tokens de acesso

Tokens de acesso são usados para solicitar as informações autenticadas de um usuário sem precisar do seu nome de usuário ou senha. Depois que o Instagram redireciona para a nossa URL de redirecionamento, nossa rota instagram() usa o código de autorização para solicitar um token de acesso do Instagram. Ela armazena o token de acesso na seção do usuário – mais uma vez, também pode ser em um banco de dados etc. – e redireciona o usuário de volta para a rota index() no HomeController.

A partir da ação index(), verificamos se o token está presente na seção. Se ele estiver, nós o recuperamos.

$instagramToken = $request->session()->get('instagramToken');

A partir daqui, é só descer a rampa. A biblioteca league/oauth2-client fornece um método de conveniência para solicitar detalhes sobre o proprietário do recurso (o usuário autenticado).

$instagramUser = Instagram::getResourceOwner($instagramToken);

A biblioteca também fornece uma maneira fácil de criar uma solicitação autenticada usando o token de acesso. O pedido retornado implementa Psr\Http\Message\RequestInterface, para que ele possa ser usado com qualquer cliente HTTP compatível com PSR-7[5]. Aqui, usamos Guzzle[6].

$feedRequest = Instagram::getAuthenticatedRequest(
    'GET',
    'https://api.instagram.com/v1/users/self/media/recent',
    $instagramToken
);

$client = new \GuzzleHttp\Client();
$feedResponse = $client->send($feedRequest);
$instagramFeed = json_decode($feedResponse->getBody()->getContents());

A variável $instagramFeed contém um objeto que representa os itens no feed de mídia recente do Instagram do usuário. Ao retornar a exibição (linhas 52-56 da Listagem 2), passamos isso como uma variável de visualização, que usamos em nossa visualização para exibir o feed de imagens do usuário, como podemos ver na Figura 3.

@forelse ($instagramFeed->data as $item)
    <div style="float: left; padding: 10px;">
        <a href="{{{ $item->link }}}">
            <img src="{{{ $item->images->thumbnail->url }}}">
        </a>
    </div>
@empty
    <p>No media found. Upload some photos to Instagram.</p>
@endforelse

3Figura 3: Mídia recente do Instagram em nosso website

Nesse ponto, o fluxo do OAuth 2.0 acabou e estamos fazendo solicitações para a API Instagram usando o token de acesso e o cliente HTTP de nossa escolha.

O código-fonte do trabalho completo para este exemplo de aplicação OAuth 2.0 está disponível para download no GitHub em https://github.com/ramsey/oauth2-exemplo/releases/imasters.

Isso parece ser um trabalho muito grande apenas para permitir que um usuário acesse seus dados do Instagram a partir do nosso website. O OAuth 2.0 pode parecer complicado e complexo. Não é um protocolo. Em vez disso, é um framework de autorização. Um exemplo onde isso pega para muitos desenvolvedores é aqui, na implementação do Instagram. O Instagram não suporta o uso do header Authorization. Ao desenvolver a biblioteca de fornecimento league/oauth2-instagram, precisávamos divergir da implementação genérica em league/oauth2-client (que usa o header Authorization) e simplesmente adicionar o parâmetro access_token à sequência de consulta em cada solicitação, como o Instagram requer.

Então, por que todo esse barulho? Por que todos os hoops para saltar? Por que não basta passar o nome de usuário e senha como parâmetros de API em HTTPS?

Uma breve história da autorização web

Há muito, muito tempo, na era da Web 1.0, não nos preocupávamos muito com isso. A maioria dos sites era reservatório de informações, e poucos deles compartilhavam dados entre domínios. A Web 2.0 mudou tudo isso com um explosão de APIs. Pela primeira vez, um usuário pôde fazer login em um site e acessar seus dados em outro, e nós fizemos alguns mash-ups muito legais com esses dados. Geramos nuvens de tags, analisamos posição social entre pares e muito mais. O único problema era que os usuários tinham que nos confiar seus nomes de usuário e senhas. Essa era a única maneira pela qual podíamos acessar seus dados através das APIs.

A palavra chave aqui é a confiança. Os usuários começaram a confiar em nós com seus nomes de usuários e senhas, e nós facilitamos isso.

Isso ficou conhecido como a senha anti-padrão[7]. Os usuários aprenderam um mau hábito. Eles aprenderam que ao desistir de informações muito sensíveis – seu nome de usuário e senha –, eles poderiam ver algumas visualizações bacanas de seus dados. Nomes de usuários e senhas, enquanto protegem informações importantes, tornaram-se uma moeda de ordenação e os atacantes perceberam que poderiam tirar vantagem de muitos alvos fáceis.

A maioria das pessoas não usa senhas seguras, e elas tendem a usar o mesmo nome de usuário e senha para todas as suas contas – redes sociais, bem como contas bancárias. Os atacantes/agressores exploraram isso criando mash-ups atraentes. Eles não precisavam gastar ciclos caros com ataques de força bruta. Nós voluntariamente lhes demos as nossas credenciais para ver coisas bacanas!

À medida que a web amadurecia, ficou claro que precisávamos de alguma forma de autorização delegada para proteger os usuários, permitindo-lhes dar acesso para outros sites aos seus dados. Assim, o OAuth nasceu.

O que é OAuth 2.0?

OAuth 2.0 é codificado em RFC 6749[8], onde é referido como um “framework de autorização”, em vez de um protocolo. Ou seja, ele define um framework para autorização, juntamente com convenções comuns, mas deixa intencionalmente muitas das especificidades abertas para interpretação.

A seção 1.8 do RFC 6749 afirma:

No entanto, como um framework rico e altamente extensível com muitos componentes opcionais, por si só, esta especificação é susceptível de produzir uma vasta variedade de implementações não interoperáveis.

Isso leva a muitas implementações, todas familiares mas ligeiramente variadas, e este é o problema que a league/oauth2-client pretende resolver.

O framework do OAuth 2.0 define quatro funções e quatro tipos de concessão de autorização. Compreender isso é crucial para entender o OAuth.

O proprietário do recurso é a pessoa ou entidade capaz de conceder permissão a um recurso protegido. Geralmente, é um usuário que tem dados que um website deseja acessar. Por exemplo, se um website deseja acessar as fotos do Instagram de um usuário, o usuário que pode conceder acesso às suas fotos é o proprietário do recurso.

O servidor de recursos é aquele que hospeda o(s) recurso(s) protegido(s). Este servidor pode aceitar e responder a solicitações de recursos usando tokens de acesso. No caso do exemplo do Instagram, a API Instagram age como o servidor de recursos.

O cliente se refere a qualquer aplicativo que usa a autorização de um proprietário de recurso para fazer solicitações de um servidor de recursos para recursos protegidos. Se um website quiser acessar uma conta de usuário do Instagram, esse website é o cliente.

Finalmente, o servidor de autorização é o servidor que concede tokens de acesso ao cliente após autenticar com o proprietário do recurso. Às vezes, ele é o mesmo que o servidor de recursos, mas eles podem ser separados. O Instagram usa o mesmo domínio que sua API; a URL para acessar seu servidor de autorização é https://api.instagram.com/oauth/authorize. Um cliente solicitando acesso a recursos protegidos (por exemplo, fotos em uma conta do Instagram) redirecionará o proprietário do recurso para essa URL, pedindo-lhe para autenticar com o Instagram e conceder acesso.

Uma concessão de autorização permite que um cliente solicite acesso a recursos protegidos com a autorização do proprietário. Os quatro tipos de concessão de autorização oferecem flexibilidade para implementações OAuth, e o  OAuth permite que tipos de concessão adicionais sejam especificados para extensão futura.

Para muitos provedores, o GenericProvider na biblioteca league/oauth2-client é suficiente para suportar a maioria dos tipos de concessão do OAuth 2.0. Basta especificar um punhado de parâmetros de configuração:

$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    'clientSecret' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    'redirectUri' => 'https://your.example.com/redirect-url',
    'urlAuthorize' => 'https://their.example.net/authorize',
    'urlAccessToken' => 'https://their.example.net/token',
    'urlResourceOwnerDetails' => 'https://their.example.net/api/me'
]);

Alguns provedores exigem tratamento adicional de parâmetros e solicitações. Nestes casos, estenda League\OAuth2\Client\Provider\AbstractProvider para fornecer a funcionalidade extra ou dê uma olhada em um dos muitos provedores de terceiros que estendem a biblioteca league/oauth2-client.

Código de autorização

O tipo de concessão de código de autorização é talvez o tipo mais comum. Nós o usamos no exemplo do Instagram. É o que é comumente referido como OAuth de três pernas, uma vez que são três partes envolvidas: proprietário do recurso, servidor de autorização e cliente. A Figura 4 ilustra o fluxo de concessão de código de autorização.

4Figura 4: Concessão de código de autorização

Na etapa 1, o proprietário do recurso pede ao cliente para acessar um recurso protegido que ele possui. O cliente, então, redireciona o proprietário do recurso para o servidor de autorização (etapa 2). O proprietário do recurso usa suas credenciais para autenticar e conceder acesso ao cliente, e o servidor de autorização redireciona o proprietário do recurso de volta para o cliente com um token de acesso (etapa 3). Agora, o cliente pode usar o token de acesso para solicitar um recurso protegido do servidor de recursos (etapa 4).

Nós cobrimos o tipo de concessão de código de autorização em profundidade para o exemplo do Instagram, mas, para recapitular usando a biblioteca league/oauth2-client, ele funciona assim:

  1. Redirecione o usuário para o provedor
  2. O provedor redireciona de volta para o nosso redirectUri
  3. Troque o código de autorização por um token de acesso
  4. Use o token de acesso para fazer solicitações autenticadas

No código, essas etapas são semelhantes às seguintes:

$authorizationUrl = $provider->getAuthorizationUrl();

// Redirect user to $authorizationUrl;
// provider redirects back after authorizing user.

$accessToken = $provider->getAccessToken('authorization_code', [
    'code' => $_GET['code']
]);

$request = $provider->getAuthenticatedRequest(
    'GET',
    'https://their.example.net/api/',
    $accessToken
);

$client = new \GuzzleHttp\Client();
$response = $client->send($request);

Credenciais de senha do proprietário do recurso

O tipo de concessão de credenciais de senha do proprietário do recurso envolve um alto grau de confiança sendo colocado no cliente. Neste tipo de concessão, o proprietário do recurso fornece seu nome de usuário e senha para o cliente, e o cliente os envia ao servidor de autorização uma vez, trocando-os por um token de acesso.

Um bom exemplo de onde o tipo de concessão de credenciais de senha do proprietário do recurso é aceitável é quando o cliente for um sistema operacional ou um aplicativo confiável em execução em um dispositivo pertencente ao proprietário do recurso. Use este tipo de concessão com extremo cuidado.

5Figura 5: Concessão de credenciais de senha do proprietário do recurso

Conforme ilustra a Figura 5, o proprietário do recurso fornece seu nome de usuário e senha para o cliente n etapa 1. Na etapa 2, o cliente troca o nome de usuário e senha por um token de acesso. O token de acesso pode, então, ser usado na etapa 3 para solicitar dados autenticados do servidor de recursos.

Para provedores que suportam esse tipo de concessão, a biblioteca league/oauth2-client torna possível usar esse fluxo em seus aplicativos PHP. Novamente, faça-o com extremo cuidado e cautela, pois esse tipo de concessão pode perpetuar a senha antipadrão.

$accessToken = $provider->getAccessToken('password', [
    'username' => 'demouser',
    'password' => 'testpass'
]);

Credenciais do cliente

Às vezes, o próprio cliente é o proprietário do recurso, e o objetivo é mostrar aos usuários dados da fonte de um terceiro. Isso é comum, por exemplo, com APIs que lidam com dados meteorológicos ou dados de mapa. O tipo de concessão de credenciais do cliente pode ser usado nesse caso.

Esse tipo de concessão funciona de modo muito semelhante ao tipo de concessão de credenciais de senha do proprietário do recurso, mas nós não solicitamos nenhuma credencial ao usuário. Nós estamos usando as próprias credenciais do cliente, que estão armazenadas no servidor.

6Figura 6: Concessão de credenciais do cliente

Na Figura 6, vemos que o usuário não fornece nenhuma credencial. Em vez disso, o cliente envia suas próprias credenciais (ID do cliente e segredo) ao servidor de autorização (etapa 1) em troca do token de acesso, que pode então usar (etapa 2) para solicitar dados autenticados da API.

Uma vez que a ID do cliente e o segredo são passados para o construtor do provedor na biblioteca league/oauth2-client, o objeto do provedor já tem as credenciais, e nós simplesmente as trocamos por um token de acesso:

$accessToken = $provider->getAccessToken('client_credentials');

Implícita

O tipo final é o tipo de concessão implícita. Esse tipo de concessão é otimizado para aplicativos do lado do cliente. Ele não usa um segredo de cliente e ocorre inteiramente dentro do navegador. Isso significa que a biblioteca league/oauth2-client não pode suportar esse tipo de concessão, por isso não vamos abordar isso com detalhes.

Em resumo, a concessão implícita depende de uma URL de redirecionamento conhecida para o identificador de cliente. O cliente redireciona para o servidor de autorização, especificando sua ID de cliente e URL de redirecionamento. O servidor de autorização verifica a URL de redirecionamento para a ID do cliente contra as URLs de redirecionamento conhecidas para essa ID. Se todas as verificações forem feitas, o usuário autentica, e o servidor de autorização redireciona de volta ao cliente com um token de acesso.

Para uma web mais segura

O OAuth 2.0 fornece um framework para a autorização web. É um passo ao longo de nossa jornada para criar uma web mais amigável e mais segura. Embora seja difícil mostrar e explicar em fragmentos de código concisos, espero que os exercícios e os exemplos mostrados aqui conduzam a uma melhor compreensão do fluxo mais comum do OAuth 2.0, a concessão do código de autorização, a razão pela qual o OAuth foi criado e como usar a biblioteca league/oauth2-client.

Listagens

Listagem 2. app/Http/Controllers/HomeController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Instagram;

class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function index(Request $request)
    {
        $instagramUser = null;
        $instagramFeed = null;

        if ($request->session()->has('instagramToken')) {
            $instagramToken = $request->session()->get('instagramToken');

            $instagramUser = Instagram::getResourceOwner($instagramToken);

            $feedRequest = Instagram::getAuthenticatedRequest(
                'GET',
                'https://api.instagram.com/v1/users/self/media/recent',
                $instagramToken
            );

            $client = new \GuzzleHttp\Client();
            $feedResponse = $client->send($feedRequest);
            $instagramFeed = json_decode($feedResponse->getBody()->getContents());
        }

        $redirectionHandler = function ($url, $provider) use ($request) {
            $request->session()->put(
                'instagramState',
                $provider->getState()
            );

            return $url;
        };

        $authUrl = Instagram::authorize([], $redirectionHandler);

        return view('home', [
            'instagramAuthUrl' => $authUrl,
            'instagramUser' => $instagramUser,
            'instagramFeed' => $instagramFeed,
        ]);
    }

    public function instagram(Request $request)
    {
        if ($request->session()->has('instagramToken')) {
            return redirect()->action('HomeController@index');
        }

        if (!$request->has('state')
            || $request->state !== $request->session()->get('instagramState')
        ) {
            abort(400, 'Invalid state');
        }

        if (!$request->has('code')) {
            abort(400, 'Authorization code not available');
        }

        $token = Instagram::getAccessToken('authorization_code', [
            'code' => $request->code,
        ]);

        $request->session()->put('instagramToken', $token);

        return redirect()->action('HomeController@index');
    }

    public function forgetInstagram(Request $request)
    {
        $request->session()->forget('instagramToken');

        return redirect()->action('HomeController@index');
    }
}

Listagem 3: resources/views/home.blade.php

@extends('layouts.app')

@section('content')
<div class="container spark-screen">
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-heading">Dashboard</div>

                <div class="panel-body">
                    You are logged in!

                    @if ($instagramUser)

                        <h1>Hello, {{{ $instagramUser->getName() }}}</h1>
                        <p>{{{ $instagramUser->getDescription() }}}</p>
                        <p><a href="/forget-instagram">Forget Instagram token</a></p>

                        <h2>Your Instagram Media</h2>
                        @forelse ($instagramFeed->data as $item)
                            <div style="float: left; padding: 10px;">
                                <a href="{{{ $item->link }}}">
                                    <img src="{{{ $item->images->thumbnail->url }}}">
                                </a>
                            </div>
                        @empty
                            <p>No media found. Upload some photos to Instagram.</p>
                        @endforelse

                    @else
                        <p>
                            <a href="{{{ $instagramAuthUrl }}}">
                                Click here to authorize with Instagram
                            </a>
                        </p>
                    @endif
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Última atualização em 30 de novembro de 2016, às 16:43:05 PST

[1] Laravel framework: https://laravel.com

[2] league/oauth2-client: http://oauth2-client.thephpleague.com

[3] Instagram Developer Documentation: https://www.instagram.com/developer

[4] league/oauth2-instagram: https://github.com/thephpleague/oauth2-instagram

[5] PSR-7: http://www.php-fig.org/psr/psr-7

[6] Guzzle: http://guzzlephp.org

[7] The Password Anti-Pattern: https://agentile.com/the-password-anti-pattern

[8] OAuth 2.0 Specification: https://tools.ietf.org/html/rfc6749

***

Ben Ramsey faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela Redação iMasters, com autorização do autor.

Artigo original publicado na revista PHP Arch.