2022-01-04 12:03:52 +00:00
# Angel3 OAuth2 Server
![Pub Version (including pre-releases) ](https://img.shields.io/pub/v/angel3_oauth2?include_prereleases )
2021-05-30 00:46:13 +00:00
[![Null Safety ](https://img.shields.io/badge/null-safety-brightgreen )](https://dart.dev/null-safety)
[![Gitter ](https://img.shields.io/gitter/room/angel_dart/discussion )](https://gitter.im/angel_dart/discussion)
2022-01-04 12:03:52 +00:00
[![License ](https://img.shields.io/github/license/dukefirehawk/angel )](https://github.com/dukefirehawk/angel/tree/master/packages/oauth2/LICENSE)
2021-05-30 00:46:13 +00:00
2023-12-24 16:10:10 +00:00
A class containing handlers that can be used within [Angel ](https://angel3-framework.web.app/ ) to build a spec-compliant OAuth 2.0 server, including PKCE support.
2018-12-15 08:39:04 +00:00
2022-01-04 12:03:52 +00:00
- [Angel3 OAuth2 Server ](#angel3-oauth2-server )
- [Installation ](#installation )
- [Usage ](#usage )
- [Other Grants ](#other-grants )
- [PKCE ](#pkce )
## Installation
2017-09-29 02:16:44 +00:00
In your `pubspec.yaml` :
```yaml
dependencies:
2023-10-08 03:29:28 +00:00
angel3_framework: ^8.0.0
angel3_oauth2: ^8.0.0
2017-09-29 02:16:44 +00:00
```
2022-01-04 12:03:52 +00:00
## Usage
2017-09-29 02:16:44 +00:00
Your server needs to have definitions of at least two types:
2022-01-04 12:03:52 +00:00
- One model that represents a third-party application (client) trying to access a user's profile.
- One that represents a user logged into the application.
2017-09-29 02:16:44 +00:00
Define a server class as such:
```dart
2021-05-30 00:46:13 +00:00
import 'package:angel3_oauth2/angel3_oauth2.dart' as oauth2;
2017-09-29 02:16:44 +00:00
2017-10-16 06:38:46 +00:00
class MyServer extends oauth2.AuthorizationServer< Client , User > {}
2017-09-29 02:16:44 +00:00
```
2022-01-04 12:03:52 +00:00
Then, implement the `findClient` and `verifyClient` to ensure that the server class can not only identify a client application via a `client_id` , but that it can also verify its identity via a `client_secret` .
2017-09-29 02:16:44 +00:00
```dart
2017-10-16 06:38:46 +00:00
class _Server extends AuthorizationServer< PseudoApplication , Map > {
2019-05-02 07:28:38 +00:00
final Uuid _uuid = Uuid();
2017-09-29 02:16:44 +00:00
@override
FutureOr< PseudoApplication > findClient(String clientId) {
return clientId == pseudoApplication.id ? pseudoApplication : null;
}
@override
Future< bool > verifyClient(
PseudoApplication client, String clientSecret) async {
return client.secret == clientSecret;
}
}
```
2022-01-04 12:03:52 +00:00
Next, write some logic to be executed whenever a user visits the authorization endpoint. In many cases, you will want to show a dialog:
2017-09-29 02:16:44 +00:00
```dart
@override
2017-10-16 06:38:46 +00:00
Future requestAuthorizationCode(
2017-09-29 02:16:44 +00:00
PseudoApplication client,
String redirectUri,
Iterable< String > scopes,
String state,
RequestContext req,
ResponseContext res) async {
res.render('dialog');
}
```
2022-01-04 12:03:52 +00:00
Now, write logic that exchanges an authorization code for an access token, and optionally, a refresh token.
2017-09-29 02:16:44 +00:00
```dart
@override
Future< AuthorizationCodeResponse > exchangeAuthCodeForAccessToken(
String authCode,
String redirectUri,
RequestContext req,
ResponseContext res) async {
2019-05-02 07:28:38 +00:00
return AuthorizationCodeResponse('foo', refreshToken: 'bar');
2017-09-29 02:16:44 +00:00
}
```
Now, set up some routes to point the server.
```dart
void pseudoCode() {
app.group('/oauth2', (router) {
router
..get('/authorize', server.authorizationEndpoint)
..post('/token', server.tokenEndpoint);
});
}
```
2017-10-16 06:38:46 +00:00
The `authorizationEndpoint` and `tokenEndpoint` handle all OAuth2 grant types.
## Other Grants
2022-01-04 12:03:52 +00:00
By default, all OAuth2 grant methods will throw a `405 Method Not Allowed` error. To support any specific grant type, all you need to do is implement the method. The following are available, not including authorization code grant support (mentioned above):
- `implicitGrant`
- `resourceOwnerPasswordCredentialsGrant`
- `clientCredentialsGrant`
- `deviceCodeGrant`
Read the [OAuth2 specification ](https://tools.ietf.org/html/rfc6749 ) for in-depth information on each grant type.
2018-12-15 08:39:04 +00:00
## PKCE
2022-01-04 12:03:52 +00:00
2018-12-15 08:39:04 +00:00
In some cases, you will be using OAuth2 on a mobile device, or on some other
public client, where the client cannot have a client
secret.
2022-01-04 12:03:52 +00:00
In such a case, you may consider using [PKCE ](https://tools.ietf.org/html/rfc7636 ).
2018-12-15 08:39:04 +00:00
2022-01-04 12:03:52 +00:00
Both the `authorizationEndpoint` and `tokenEndpoint` inject a `Pkce` factory into the request, so it
2018-12-15 08:39:04 +00:00
can be used as follows:
```dart
@override
Future requestAuthorizationCode(
PseudoApplication client,
String redirectUri,
Iterable< String > scopes,
String state,
RequestContext req,
ResponseContext res) async {
// Automatically throws an error if the request doesn't contain the
// necessary information.
var pkce = req.container.make< Pkce > ();
// At this point, store `pkce.codeChallenge` and `pkce.codeChallengeMethod` ,
// so that when it's time to exchange the auth code for a token, we can
2019-05-02 07:28:38 +00:00
// create a [Pkce] object, and verify the client.
2018-12-15 08:39:04 +00:00
return await getAuthCodeSomehow(client, pkce.codeChallenge, pkce.codeChallengeMethod);
}
@override
Future< AuthorizationTokenResponse > exchangeAuthorizationCodeForToken(
String authCode,
String redirectUri,
RequestContext req,
ResponseContext res) async {
// When exchanging the authorization code for a token, we'll need
// a `code_verifier` from the client, so that we can ensure
// that the correct client is trying to use the auth code.
//
// If none is present, an OAuth2 exception is thrown.
var codeVerifier = await getPkceCodeVerifier(req);
// Next, we'll need to retrieve the code challenge and code challenge method
// from earlier.
var codeChallenge = await getTheChallenge();
var codeChallengeMethod = await getTheChallengeMethod();
2019-05-02 07:28:38 +00:00
// Make a [Pkce] object.
var pkce = Pkce(codeChallengeMethod, codeChallenge);
2018-12-15 08:39:04 +00:00
// Call `validate` . If the client is invalid, it throws an OAuth2 exception.
pkce.validate(codeVerifier);
// If we reach here, we know that the `code_verifier` was valid,
// so we can return our authorization token as per usual.
2019-05-02 07:28:38 +00:00
return AuthorizationTokenResponse('...');
2018-12-15 08:39:04 +00:00
}
2022-01-04 12:03:52 +00:00
```