oauth2_client: implement OAuth2 clients with Flutter
Tags: oauth, oauth2, flutter, dart
Published: 2020-04-06 21:33:47

In the first part of this article I will give you an overview of the OAuth 2 standard, in particular I will introduce the two most used grants, the Client Credentials and the Authorization Code grants.
In the second part I will introduce oauth2_client, a Flutter library that considerably simplifies working with OAuth 2.
By the end of the article you will be able to implement your own client with just little lines of code.

If you are already familiar with the concepts behind OAuth2, you can directly skip to the [hands on section](#hands-on), otherwise keep reading...

### What is OAuth 2?

[OAuth 2](https://oauth.net/2/) is an open standard protocol that allows applications to access  protected resources of a service on behalf of the user. OAuth 2 defines authorization flows for native apps, web apps and for mobile devices.

Many companies offer OAuth 2 endpoints: these include the _usual suspects_, such as Google, Facebook, LinkedIn, GitHub, as well as many other.

Generally speaking, an authorization process can be carried on as follow:

1. The application sends to the authorization server an Authorization Request to access a protected resource
2. The owner of the resource (usually, the user) grants access to it
3. The authorization server sends back an Access Token to be used with all the subsequent requests as a sort of authorization badge

This is just an high level depiction of the whole process. The actual flow will differ depending on the authorization grant type in use.

The two most used grant types are the [Client Credentials](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant and the [Authorization Code](https://www.oauth.com/oauth2-servers/access-tokens/authorization-code-request/) grant.

#### The Client Credentials grant

The _Client Credentials_ grant is primarily used on machine-to-machine applications, such as daemons, services or CLI. In this case the authorization server grants access to the application itself rather than to the user.

To use this flow, an application must have been assigned a _client ID_ and a _client secret_. These parameters are generated by the authorization server, and are needed to ensure that the client connecting is the one that has been authorized.

The authorization process works more or less like this:

1. The application sends to the authorization server an Authorization Request along with the _client id_ and the _client secret_.
2. The authorization server checks the provided credentials
3. The authorization server sends back to the application an _Access Token_, along with a _Referesh Token_.
4. The application uses the _Access Token_ in every subsequent request to the service.

![Client Credentials grant](https://www.teranet.it/img/articoli/client_credentials_grant.png)

#### The Authorization Code grant

The _Authorization Code_ grant is by far the most used flow as it is employed by applications that effectively operate on some user owned resources.

To use this flow, the application must have been registered with the OAuth server, providing at least a name and a _redirect URI_, that will be used to inform the application that the user authorized the request. During the registration, the provider assigns the application a _client ID_ and a _client secret_, that will be exchanged in the authorization process.

To access a specific resource, the application should specify one or more _scopes_, a list of strings that represent the "permissions" the application is requiring. The scopes are defined by the resource server and are normally included in the API documentation of the service.

This flow can be summarized like this:

1. The user opens up the app
2. The app opens up a browser instance pointing to the authorization endpoint on the authorization server, requiring permission to access the needed resources, and passing in the Client ID and the list of required scopes.
3. The user authenticates with the authorization server and grants access to the resources
4. The authorization server redirects the client to the _redirect uri_ sending an _Authorization Token_
5. The client app issues an _Access Token Request_, passing in the Authorization Token and the client secret.
6. The server validates the client secret and the Authorization Token and sends back an _Access Token_ and a _Refresh Token_
7. The client app uses the _Access Token_ in every subsequent request to the API service as a sort of authorization badge.

![Authorization Code grant](https://www.teranet.it/img/articoli/authorization_code_grant.png)

The OAuth 2 framework has been designed to be extensible, and as such it underwent some additions since its inception, for example to address potential security flaws or extend its functionalities. One of these is the [PKCE](https://oauth.net/2/pkce/) extension, designed to increase the Authorization Code grant security through an exchange of a client generated code that limits the chances of the authorization code to be intercepted.

#### Access Token expiration / invalidation

To limit the risks of token hijacking, the Access Token often has a limited lifespan after which it cannot be used anymore. Moreover, the Access Token could be explicitly invalidated by the resource server.

When the token expires, the application must require another Access Token using another grant type, the _Refresh Token_ grant. As the name suggests, this flow uses the _Refresh Token_ returned by the previous authorization flows to generate a new Access Token that must be used from that moment onward.

## Hands on! <a name="hands-on"></a>

By now it should be clear that to implement an OAuth 2 capable application, one must implement not only the authorization process provided by the service he is going to use, but even the mechanisms to deal with the expiration or invalidation of the Access Token. These can easily become quite wasteful activities!

[oauth2_client](https://pub.dev/packages/oauth2_client) is a Flutter library specifically created with the purpose of simplifying the process of requiring and refreshing the OAuth 2 access token. It provides pre-made classes to authenticate againts the leading providers, such as Google, Facebook, LinkedIn, GitHub, but it's particularly suited for implementing clients for custom OAuth 2 servers.

It additionally provides convenience methods for secure token storage, automatic token refreshing and to transparently perform authenticated HTTP requests.

Suppose we wanted to implement an app with the GitHub API. As per their [API documentation](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/), GitHub  recommends on using the Web application flow (with the _Authorization Code_ grant).
This means that one of the steps of the process involves opening up a browser instance and intercepting a redirection to a provided redirect uri.

If one of the targets of our app is Android, we first need to set up an Intent Filter in the AndroidManifest.xml file to define a custom uri scheme that will be intercepted by our app:

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mypackage">
	...
	<activity android:name="com.linusu.flutter_web_auth.CallbackActivity" >
		<intent-filter android:label="flutter_web_auth">
			<action android:name="android.intent.action.VIEW" />
			<category android:name="android.intent.category.DEFAULT" />
			<category android:name="android.intent.category.BROWSABLE" />
			<data android:scheme="my.app" />
		</intent-filter>
	</activity>
</manifest>
```

The value of the _android:scheme_ attribute can be anything you want, and must match the scheme used for the redirect uri (as we'll see later). Just make sure, if at all possible, **not to use http(s)**, because in that case our application won't be able to intercept the server redirection, as it will be automatically handled by the system browser.

If you really want/need to **use** an **HTTPS** url as the redirect uri, you must setup it as an [App Link](https://developer.android.com/training/app-links/index.html).
First you need to specify both the android:host and android:pathPrefix attributes, as long as the _android:autoVerify="true"_ attribute on the intent-filter tag:

```xml
<activity android:name="com.linusu.flutter_web_auth.CallbackActivity" >
	<intent-filter android:label="flutter_web_auth" android:autoVerify="true">
		<action android:name="android.intent.action.VIEW" />
		<category android:name="android.intent.category.DEFAULT" />
		<category android:name="android.intent.category.BROWSABLE" />

		<data android:scheme="https"
				android:host="www.myapp.com"
				android:pathPrefix="/oauth2redirect" />
	</intent-filter>
</activity>
```

Then you need to [prove ownership](https://developer.android.com/training/app-links/verify-site-associations) of the domain host by publishing a [Digital Asset Links](https://developers.google.com/digital-asset-links/v1/getting-started) JSON file on your website. This involves generating an [App signing key](https://developer.android.com/studio/publish/app-signing) and signing your app with it. 

Our sample app will retrieve the list of the user's repositories. To do so, we must tell the server which "permissions" we'll need, using the _scope_ parameter. The scope that requires access to the user's repositories (both public and private)  is "repo".

Before you can interact with GitHub's OAuth 2 endpoint, you must [create a new OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). After this step your application will be assigned a _clientId_ and a _clientSecret_.

Ok, enough of talking... Let's see some code!

```dart
import 'package:oauth2_client/github_oauth2_client.dart';
import 'package:oauth2_client/access_token_response.dart';

//Instantiate the GitHub client
OAuth2Client client = GitHubOAuth2Client(
	//Corresponds to the android:scheme attribute
	customUriScheme: 'my.app',
	//The scheme must match the customUriScheme parameter!
	redirectUri: 'my.app://oauth2redirect'
);

//Require an Access Token with the Authorization Code grant
AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
	clientId: 'myclientid',
	clientSecret: 'myclientsecret',
	scopes: ['repo']);

//From now on you can perform authenticated HTTP requests
httpClient = http.Client()
http.Response resp = await httpClient.get('https://api.github.com/user/repos',
	headers: {'Authorization': 'Bearer ' + tknResp.accessToken});

//The list of the user repositories is encoded in the resp.body property...

```

As you see, fetching an Access Token is quite simple, and since GitHub tokens don't expire we should be all set... But what happens if the token gets invalidated by the app owner? You would need to check the validity of the token after every request and possibly fetch another token. Something like this:

```dart
...
httpClient = http.Client()
http.Response resp = await httpClient.get('https://api.github.com/user/repos',
	headers: {'Authorization': 'Bearer ' + tknResp.accessToken});

//The request to the service didn't succeed...
//We must refresh the token (not supported by GitHub endpoint) or
//generate a new Access Token
if(resp.statusCode != 200) {
	AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
		clientId: 'myclientid',
		clientSecret: 'myclientsecret',
		scopes: ['repo']);

	//Perform another request to the server with the new access token
	http.Response resp = await httpClient.get('https://api.github.com/user/repos',
		headers: {'Authorization': 'Bearer ' + tknResp.accessToken});
}
```

Fortunately enough, the library provides an helper class that takes care of all of the burden:

```dart
//Instantiate the client...
OAuth2Client client = GitHubOAuth2Client(
	redirectUri: 'my.app://oauth2redirect',
	customUriScheme: 'my.app');

//Instantiate the helper passing it the client and passing in the authorization parameters
OAuth2Helper oauth2Helper = OAuth2Helper(client,
	grantType: OAuth2Helper.AUTHORIZATION_CODE, //default value, can be omitted
	clientId: 'myclientid',
	clientSecret: 'myclientsecret',
	scopes: ['repo']);

//Perform a request to the server
http.Response resp = await oauth2Helper.get('https://api.github.com/user/repos');
```

As long as the first request is made, the helper class performs the following operations:
* Checks if an Access Token already exists in the device storage
* If it doesn't exist, generates an Access Token using the required flow (Authorization Code when not specified) and stores it into the device secure storage.
* Sends the request to the server, adding the "Authorization" header with the access token
* If the server returns an "unauthorized" response, try to refresh the Access Token through the Refresh Token flow, if the service supports it, otherwise generate a new token. Finally, send another request with the new token.

As you see, not only the helper class takes care of the token retrieval/refresh process, it even stores the token in a secure storage on the device. This means that if your app is closed or the device is restarted, the retrieved tokens are still available and can be used without requiring a new authorization process.

### _That's all well and good, but what if I wanted to implement my own client?_

Up until now, we saw how to use a predefined client class, but what if you needed to authenticate against a service that doesn't have a dedicated class?

Implementing your own clients is really simple, and normally requires only a couple of lines of code. For example, let's see how the GitHub client is made:

```dart
class GitHubOAuth2Client extends OAuth2Client {
  GitHubOAuth2Client(
      {@required String redirectUri, @required String customUriScheme})
      : super(
            authorizeUrl: 'https://github.com/login/oauth/authorize',
            tokenUrl: 'https://github.com/login/oauth/access_token',
            redirectUri: redirectUri,
            customUriScheme: customUriScheme) {

	  //By default, GitHub responds to an Access Token request with a querystring-like
	  //format. We need a json formatted response, that can be required through
	  //the 'Accept' header...
      this.accessTokenRequestHeaders = {
        'Accept': 'application/json'
      };

  }
}
```

That's it! You really just need to extend the OAuth2Client class and provide the authorization and the token urls.
After that, you can use your client or even the helper as seen above, without any modifications.

## Finally... The inevitable TL;DR
oauth2_client really simplifies working with OAuth 2 endpoints, as it takes care of all the peculiarities of the protocol and automates the process of refreshing the tokens upon expiration. It even transparently implements many of the best practices introduced to enforce the security and reliability of the process.