Net::O...Manual(3pm)

πŸ“¦
version 0.28 Β· 3 versions
, in ubuntu noble, ubuntu resolute, ubuntu stonking

Net::OAuth2::AuthorizationServer::Manual(3pm) β€” bestie, this is the manual for using Net::OAuth2::AuthorizationServer, under User Contributed Perl Documentation. it's giving "how do I OAuth2 without losing my whole mind" πŸ’–

✨ NAME ✨

Net::OAuth2::AuthorizationServer::Manual β€” how to use Net::OAuth2::AuthorizationServer, periodt πŸ’…

πŸ¦‹ DESCRIPTION πŸ¦‹

okay so the modules chilling inside the Net::OAuth2::AuthorizationServer namespace are the ones that implement the various OAuth2 grant type flows. Each module handles one specific grant flow and is designed to "just work" with, like, minimal detail and effort. iconic behavior, honestly.

But bestie, there are some limitations when you use the grant modules in a minimal way: no ability to store tokens across restarts or multi-proc, and you lose tokens over a restart. not the vibe. You can get around these limitations by supplying a jwt_secret or by supplying your own overrides. The overrides are detailed in the "CALLBACK FUNCTIONS" section below β€” each grant module's docs lists the supported callback functions.

If you would still like to use the grant modules in an easy way, but also have tokens persistent across restarts and shared between multi processes, then you can supply a jwt_secret. What you lose when doing this is the ability for tokens to be revoked. You could implement the verify_auth_code and verify_access_token methods to handle the revoking in your app. So that would be halfway between the "simple" and the "realistic" way. "CLIENT SECRET, TOKEN SECURITY, AND JWT" has more detail about JWTs. no cap.

WHICH GRANT TYPE SHOULD I USE? πŸ’…

You are advised to read <https://tools.ietf.org/html/rfc6749> and figure that out depending on your needs, but to simplify β€” if you are implementing an app server and need to give out access tokens you should be using the "Authorization Code Grant" because this is the most secure flow. If you need to do a call from javascript then the "Implicit Grant" is probably what you need. You should not use the "Password Grant" or "Client Credentials Grant" unless your client/server apps are non-public facing or you really know what you are doing. fr fr.

The "Authorization Code Grant" is used to obtain both access tokens and refresh tokens and is optimized for confidential clients. Since this is a redirection-based flow, the client must be capable of interacting with the resource owner's user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server.

The "Implicit Grant" is used to obtain access tokens (it does not support the issuance of refresh tokens) and is optimized for public clients known to operate a particular redirection URI. These clients are typically implemented in a browser using a scripting language such as JavaScript.

The "Password Grant" type MUST NOT be used according to recent draft RFC <https://tools.ietf.org/html/draft-ietf-oauth-security-topics-15>. like, do not. 🚫

The "Client Credentials Grant" can request an access token using only its client credentials (or other supported means of authentication) when the client is requesting access to the protected resources under its control, or those of another resource owner that have been previously arranged with the authorization server.

πŸŽ€ CONSTRUCTOR ARGUMENTS πŸŽ€

The following are accepted by all grant types, bestie.

clients

A hashref of client details keyed like so:

         clients => {
           $client_id => {
             client_secret => $client_secret,
             redirect_uri  => $redirect_uri,  # in the case of "Implicit Grant"
             scopes        => {
               eat       => 1,
               drink     => 0,
               sleep     => 1,
             },
           },
         },

Note that setting a client_secret implies either "Authorization Code Grant" or "Client Credentials Grant". if this is not set then "Implicit Grant" is implied (that has an optional redirect_uri parameter).

Note the clients config is not required if you add all of the necessary callback functions detailed below, but is necessary for running the grant types in their simplest form (when there are no callbacks provided).

users

In the case of the "Password Grant" you should supply a hash of user to password mappings as well as the above client details:

               users => {
                       test_user => 'reallyletmein',
               }

Again, the users config is not required if you add all of the necessary callback functions detailed below.

jwt_secret

This is optional. If set JWTs will be returned for the auth codes, access, and refresh tokens. JWTs allow you to validate tokens without doing a db lookup, but there are certain considerations (see "CLIENT SECRET, TOKEN SECURITY, AND JWT").

jwt_algorithm

This is optional, and sets the algorithm used in the creation of JWTs (see "CLIENT SECRET, TOKEN SECURITY, AND JWT").

jwt_encoding

This is optional, and sets the encoding used in the creation of JWTs (see "CLIENT SECRET, TOKEN SECURITY, AND JWT").

access_token_ttl

The validity period of the generated access token in seconds. Defaults to 3600 seconds (1 hour). yes, one whole hour.

🌈 CALLBACK FUNCTIONS 🌈

These are the callbacks necessary to use the grant modules in a more realistic way, and are required to make the auth code, access token, refresh token, etc available across several processes and persistent.

They should be passed to the grant module constructor, each should be a reference to a subroutine:

         my $Grant = Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant->new(
           ...
           confirm_by_resource_owner_cb => \&your_subroutine,
         );

The examples below use monogodb (a db helper returns a MongoDB::Database object) for the code that would be bespoke to your application β€” such as finding access codes in the database, and so on. You can refer to the tests in t/ and examples in examples/ in this distribution for how it could be done and to actually play around with the code both in a browser and on the command line.

The examples below are also using a mojo_controller object within the args hash passed to the callbacks β€” you can pass any extra keys/values you want within the args hash so you can do the necessary things (e.g. logging) along with the required args.

confirm_by_resource_owner_cb

A callback to tell the grant module if the Resource Owner allowed or denied access to the Resource Server by the Client. The args hash should contain the client id, and an array reference of scopes requested by the client.

The callback should return a list with three elements. The first element is 1 if access is allowed, 0 if access is not allowed, otherwise undef if the flow was interrupted (e.g. calling redirect in a controller). The second element should be the error message in the case of problems with the confirmation of the scopes. The third element should be an array reference of scopes as the user may choose to modify the list of requested scopes when confirming them.

         my $resource_owner_confirm_scopes_sub = sub {
           my ( %args ) = @_;

           my ( $obj,$client_id,$scopes_ref,$redirect_uri,$response_type )
               = @args{ qw/ mojo_controller client_id scopes redirect_uri response_type / };

           my $error;
           my $is_allowed = $obj->flash( "oauth_${client_id}" );

           # if user hasn't yet allowed the client access, or if they denied
           # access last time, we check [again] with the user for access
           if ( ! $is_allowed ) {
             $obj->flash( client_id => $client_id );
             $obj->flash( scopes    => $scopes_ref );

             # we need to redirect back to the /oauth/authorize route after
             # confirm/deny by resource owner (with the original params)
             my $uri = join( '?',$obj->url_for('current'),$obj->url_with->query );
             $obj->flash( 'redirect_after_login' => $uri );
             $obj->redirect_to( '/oauth/confirm_scopes' );
           }

           return ( $is_allowed,$error,$scopes_ref );
         };

Note that you need to pass on the current url (with query) so it can be returned to after the user has confirmed/denied access, and the confirm/deny result is stored in the flash (this could be stored in the user session if you do not want the user to confirm/deny every single time the Client requests access). Also note the caveat regarding flash and Path as documented above (login_resource_owner).

login_resource_owner_cb

A callback to tell the grant module if the Resource Owner is logged in. You can pass a hash of arguments should you need to do anything within the callback It should return 1 if the Resource Owner is logged in, otherwise it should do the required things to login the resource owner (e.g. redirect) and return 0:

         my $resource_owner_logged_in_sub = sub {
           my ( %args ) = @_;

           my $c = $args{mojo_controller};

           if ( ! $c->session( 'logged_in' ) ) {
             # we need to redirect back to the /oauth/authorize route after
             # login (with the original params)
             my $uri = join( '?',$c->url_for('current'),$c->url_with->query );
             $c->flash( 'redirect_after_login' => $uri );
             $c->redirect_to( '/oauth/login' );
             return 0;
           }

           return 1;
         };

Note that you need to pass on the current url (with query) so it can be returned to after the user has logged in. You can see that the flash is in use here β€” be aware that the default routes (if you don't pass them to grant module constructor) for authorize and access_token are under /oauth/ so it is possible that the flash may have a Path of /oauth/ β€” the consequence of this is that if your login route is under a different path (likely) you will not be able to access the value you set in the flash. The solution to this? Simply create another route under /oauth/ (so in this case /oauth/login) that points to the same route as the /login route.

verify_client_cb

References: <http://tools.ietf.org/html/rfc6749#section-4.1.1>, <http://tools.ietf.org/html/rfc6749#section-4.2.1>, <http://tools.ietf.org/html/rfc6749#section-4.3.1>, <http://tools.ietf.org/html/rfc6749#section-4.4.1>,

A callback to verify if the client asking for authorization is known to the authorization server and allowed to get authorization for the passed scopes.

The args hash will always contain the client id and an array reference of requested scopes. Additionally for a ClientCredentials Grant the args hash will also contain the client secret or for an Implicit Grant response type and optionally redirect uri will be present, or for a Code Grant response type will be present.

The callback should return a list with three elements. The first element is either 1 or 0 to say that the client is allowed or disallowed, the second element should be the error message in the case of the client being disallowed and the third should be the amended scopes_ref denoting the allowed scopes after filtering by the client allowed scopes:

         my $verify_client_sub = sub {
           my ( %args ) = @_;
           my ( $obj,$client_id,$scopes_ref,$client_secret,$redirect_uri,$response_type )
               = @args{ qw/ mojo_controller client_id scopes client_secret redirect_uri response_type / };

           if (my $client = $obj->db->get_collection( 'clients' )->find_one({ client_id => $client_id })) {
               my $client_scopes = [];

               # Check scopes
               foreach my $scope ( @{ $scopes_ref // [] } ) {

                 if ( ! exists( $client->{scopes}{$scope} ) ) {
                   return ( 0,'invalid_scope' );
                 } elsif ( $client->{scopes}{$scope} ) {
                   push @{$client_scopes}, $scope;
                 }
               }

               # Implicit Grant Checks
               if ( $response_type && $response_type eq 'token' ) {
                 # If 'credentials' have been assigned Implicit Grant should be prevented, so check for secret
                 return (0, 'unauthorized_grant') if $client->{'secret'};

                 # Check redirect_uri
                 return (0, 'access_denied')
                     if ($client->{'redirect_uri'} && (!$redirect_uri || $redirect_uri ne $client->{'redirect_uri'});
               }

               # Credentials Grant Checks
               if ($client_secret && $client_secret ne $client->{'secret'}) {
                   return (0, 'access_denied');
               }

               return ( 1, undef, $client_scopes );
           }

           return ( 0,'unauthorized_client' );
         };

✨ store_auth_code_cb

Okay so this one is your bestie's little save it for later moment. πŸ’Ύ It's a callback that lets you store the authorization code we just generated for you. The args hash hands you the client id, how long the auth code stays valid in seconds, the Client redirect URI, and a list of all the scopes the Client asked for. Save all that in your data store and β€” plot twist ✨ β€” the verify_auth_code callback can come along later to look it up and check it.

  my $store_auth_code_sub = sub {
    my ( %args ) = @_;

    my ( $obj,$auth_code,$client_id,$expires_in,$uri,$scopes_ref ) =
        @args{qw/ mojo_controller auth_code client_id expires_in redirect_uri scopes / };

    my $auth_codes = $obj->db->get_collection( 'auth_codes' );

    my $id = $auth_codes->insert({
      auth_code    => $auth_code,
      client_id    => $client_id,
      user_id      => $obj->session( 'user_id' ),
      expires      => time + $expires_in,
      redirect_uri => $uri,
      scope        => { map { $_ => 1 } @{ $scopes_ref // [] } },
    });

    return;
  };

✨ verify_auth_code_cb

Reference: <http://tools.ietf.org/html/rfc6749#section-4.1.3>

So like, the Client is out here sending an authorization code back to the Authorization Server, and you need to vibe-check it. πŸ” This callback does exactly that. Your args hash should come with the client_id, the client_secret, the authorization code, and the redirect uri. No cap.

Run the check using the rules in that RFC up there ‴️ and return a list with 4 elements, bestie, don't forget any of them:

  1. A client identifier (a scalar, or a reference) if the authorization code is valid β€” or a 0 if it's not valid.
  2. The error message, when the authorization code is not valid.
  3. A hash reference of the scopes as requested by the client in the original call for an authorization code.
  4. A user identifier.
  my $verify_auth_code_sub = sub {
    my ( %args ) = @_;

    my ( $obj,$client_id,$client_secret,$auth_code,$uri )
        = @args{qw/ mojo_controller client_id client_secret auth_code redirect_uri / };

    my $auth_codes      = $obj->db->get_collection( 'auth_codes' );
    my $ac              = $auth_codes->find_one({
      client_id => $client_id,
      auth_code => $auth_code,
    });

    my $client = $obj->db->get_collection( 'clients' )
      ->find_one({ client_id => $client_id });

    $client || return ( 0,'unauthorized_client' );

    if (
      ! $ac
      or $ac->{verified}
      or ( $uri ne $ac->{redirect_uri} )
      or ( $ac->{expires} <= time )
      or ( $client_secret ne $client->{client_secret} )
    ) {

      if ( $ac->{verified} ) {
        # the auth code has been used before - we must revoke the auth code
        # and access tokens
        $auth_codes->remove({ auth_code => $auth_code });
        $obj->db->get_collection( 'access_tokens' )->remove({
          access_token => $ac->{access_token}
        });
      }

      return ( 0,'invalid_grant' );
    }

    # scopes are those that were requested in the authorization request, not
    # those stored in the client (i.e. what the auth request restricted scopes
    # to and not everything the client is capable of)
    my $scope = $ac->{scope};

    $auth_codes->update( $ac,{ verified => 1 } );

    return ( $client_id,undef,$scope,$ac->{user_id} );
  };

✨ store_access_token_cb

Now this is the callback that lets you stash the generated access and refresh tokens. 🎟️ The args hash should come with the client identifier exactly as returned from the verify_auth_code callback, the authorization code, the access token, the refresh_token, the validity period in seconds, the scope returned from the verify_auth_code callback, and the old refresh token.

Heads up πŸ‘€ the authorization code that gets passed in could be undef. When that happens it means the access token and refresh tokens were asked for by the Client using an existing refresh token β€” which gets passed along in the old refresh token variable. In that case you should use the old refresh token to figure out the previous access token and revoke both the previous access and refresh tokens. (This is not a hard requirement per the OAuth spec, but honestly? I'd recommend it. πŸ’…)

The callback doesn't need to return anything. Just save it all to your data store and the verify_access_token callback can pull it back up later to verify it:

  my $store_access_token_sub = sub {
    my ( %args ) = @_;

    my (
      $obj,$client,$auth_code,$access_token,$refresh_token,
      $expires_in,$scope,$old_refresh_token
    ) = @args{qw/
      mojo_controller client_id auth_code access_token
      refresh_token expires_in scopes old_refresh_token
    / };

    my $access_tokens  = $obj->db->get_collection( 'access_tokens' );
    my $refresh_tokens = $obj->db->get_collection( 'refresh_tokens' );

    my $user_id;

    if ( ! defined( $auth_code ) && $old_refresh_token ) {
      # must have generated an access token via refresh token so revoke the old
      # access token and refresh token (also copy required data if missing)
      my $prev_rt = $obj->db->get_collection( 'refresh_tokens' )->find_one({
        refresh_token => $old_refresh_token,
      });

      my $prev_at = $obj->db->get_collection( 'access_tokens' )->find_one({
        access_token => $prev_rt->{access_token},
      });

      # access tokens can be revoked, whilst refresh tokens can remain so we
      # need to get the data from the refresh token as the access token may
      # no longer exist at the point that the refresh token is used
      $scope //= $prev_rt->{scope};
      $user_id = $prev_rt->{user_id};

      # need to revoke the access token
      $obj->db->get_collection( 'access_tokens' )
        ->remove({ access_token => $prev_at->{access_token} });

    } else {
      $user_id = $obj->db->get_collection( 'auth_codes' )->find_one({
        auth_code => $auth_code,
      })->{user_id};
    }

    if ( ref( $client ) ) {
      $scope  = $client->{scope};
      $client = $client->{client_id};
    }

    # if the client has an existing refresh token we need to revoke it
    $refresh_tokens->remove({ client_id => $client, user_id => $user_id });

    $access_tokens->insert({
      access_token  => $access_token,
      scope         => $scope,
      expires       => time + $expires_in,
      refresh_token => $refresh_token,
      client_id     => $client,
      user_id       => $user_id,
    });

    $refresh_tokens->insert({
      refresh_token => $refresh_token,
      access_token  => $access_token,
      scope         => $scope,
      client_id     => $client,
      user_id       => $user_id,
    });

    return;
  };

✨ verify_access_token_cb

Reference: <http://tools.ietf.org/html/rfc6749#section-7>

This callback verifies the access token. The args hash should come with the access token, an optional reference to a list of the scopes, and a flag for whether the access_token is actually a refresh token. 🫢

Lowkey important: the access token could be the refresh token, because this method also fires when the client uses the refresh token to get a brand new access token. When that's the case, the value of the $is_refresh_token variable will be true. 😌

Run the check against the rules in that RFC above ‴️. Return false if the access token isn't valid. If it is valid, return something useful β€” and since this method gets called by $c->oauth, you probably wanna hand back a hash of details that the access token relates to (client id, user id, etc.).

And when you get an invalid, expired, whatever access or refresh token? Return a list where the first element is 0 and the second holds the error message (which in this case is almost definitely 'invalid_grant').

  my $verify_access_token_sub = sub {
    my ( %args ) = @_;

    my ( $obj,$access_token,$scopes_ref,$is_refresh_token )
          = @args{qw/ mojo_controller access_token scopes is_refresh_token /};

    my $rt = $obj->db->get_collection( 'refresh_tokens' )->find_one({
      refresh_token => $access_token
    });

    if ( $is_refresh_token && $rt ) {

      if ( $scopes_ref ) {
        foreach my $scope ( @{ $scopes_ref // [] } ) {
          if ( ! exists( $rt->{scope}{$scope} ) or ! $rt->{scope}{$scope} ) {
            return ( 0,'invalid_grant' )
          }
        }
      }

      # $rt contains client_id, user_id, etc
      return $rt;
    }
    elsif (
      my $at = $obj->db->get_collection( 'access_tokens' )->find_one({
        access_token => $access_token,
      })
    ) {

      if ( $at->{expires} <= time ) {
        # need to revoke the access token
        $obj->db->get_collection( 'access_tokens' )
          ->remove({ access_token => $access_token });

        return ( 0,'invalid_grant' )
      } elsif ( $scopes_ref ) {

        foreach my $scope ( @{ $scopes_ref // [] } ) {
          if ( ! exists( $at->{scope}{$scope} ) or ! $at->{scope}{$scope} ) {
            return ( 0,'invalid_grant' )
          }
        }

      }

      # $at contains client_id, user_id, etc
      return $at;
    }

    return ( 0,'invalid_grant' )
  };

verify_user_password_cb

A callback that does the whole "is this actually you tho" check β€” it verifies a username and a password together. πŸ’– The args hash should contain the client_id, client_secret, username, password, and an optional reference to a list of the scopes.

Your callback should verify the client details and the username + password, and then return a hash list with 2 elements. Element one should be a hash containing the client id β€” but only if the client details + username are valid β€” plus the scopes and the username. Element two should be the error message, for when the creds are bad. 🫢

  my $verify_user_password_sub = sub {
    my ( $self, %args ) = @_;

    my ( $obj, $client_id, $client_secret, $username, $password, $scopes ) =
      @args{ qw/ mojo_controller client_id client_secret username password scopes / };

    my $client = $obj->db->get_collection( 'clients' )
      ->find_one({ client_id => $client_id });

    $client || return ( 0, 'unauthorized_client' );

    my $user = $obj->db->get_collection( 'users' )
      ->find_one({ username => $username });

    if (
      ! $user
      or $client_secret ne $client->{client_secret}
      # some routine to check the password against hashed + salted
      or ! $obj->passwords_match( $user->{password},$password )
    ) {
      return ( 0, 'invalid_grant' );
    }
    else {
      return ({
          client_id => $client_id,
          scopes    => $scopes,
          username  => $username,
      });
    }

  };

✨ PUTTING IT ALL TOGETHER ✨

Okay so once you've defined all those callbacks above β€” customised to your app, your data store, etc β€” you can go ahead and configure the grant module. πŸ’… This example is using the Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant:

  my $Grant = Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant->new(
    login_resource_owner_cb      => $resource_owner_logged_in_sub,
    confirm_by_resource_owner_cb => $resource_owner_confirm_scopes_sub,
    verify_client_cb             => $verify_client_sub,
    store_auth_code_cb           => $store_auth_code_sub,
    verify_auth_code_cb          => $verify_auth_code_sub,
    store_access_token_cb        => $store_access_token_sub,
    verify_access_token_cb       => $verify_access_token_sub,
  );

Heads up bestie: because we're using the verify_client_cb above, we do not need to pass a hashref of clients β€” that's all handled inside the verify_client_cb sub. πŸŽ€


🌸 SCOPES 🌸

Access tokens can have this whole concept of "scopes", which you can roughly translate to permissions/privileges on your application side. The RFC has all the details: <https://tools.ietf.org/html/rfc6749#section-3.3>. You've 100% seen this in action when you log into a service using a social login option β€” you get that list of things the service would like to be able to do on your behalf, and in most cases you're allowed to uncheck certain permissions from the list. Iconic behaviour honestly. ✨

The various grant types supported by the modules in this distribution fully support scopes, and the important thing to note is that the various grant types (as used with no overrides) will validate any requested scopes against the configured scopes with this logic: πŸ‘‘

  1. If a scope is requested that the client is not configured to have (does not exist in the client's scope list) then "invalid_scope" will be returned
  2. If a scope is requested that the client is not configured to use (exists in the client's scope list but is set to false) then "access_denied" wil be returned
Ngl, this may change slightly as we figure out the best implementation for various use cases when no overrides are supplied. πŸ™ˆ

πŸ‘‘ CLIENT SECRET, TOKEN SECURITY, AND JWT πŸ‘‘

The auth codes and access tokens generated by the grant modules should be unique. When jwt_secret is not supplied, they're generated using a combination of the generation time (to microsecond precision) + rand() + a call to Crypt::PRNG's random_string function. Then those get base64 encoded to make sure there are zero problems with URL encoding. πŸ¦‹

If jwt_secret is set β€” and it should be a strong secret, no cap β€” the tokens are created with the Crypt::JWT module and each token should contain a jti using a call to Crypt::PRNG's random_string function. You can decode the tokens, typically with Crypt::JWT, to get the information about the client and scopes β€” but you should not trust the token unless the signature matches. πŸ’€

If you wish to encrypt JWT, that is to say generate JWE tokens, you can set the jwt_encoding and jwt_algorithm attributes β€” these map respectively to the Crypt::JWT enc and alg attributes. See that module's POD for more info. πŸ”₯

As the JWT contains the client information and scopes, you can, in theory, use this information to validate an auth code / access token / refresh token without doing a database lookup. However, it gets somewhat more complicated when you need to revoke tokens. For more information about JWTs and revoking tokens see <https://auth0.com/blog/2015/03/10/blacklist-json-web-token-api-keys/> and <https://tools.ietf.org/html/rfc7519>. Ultimately you're going to have to use some shared store to revoke tokens, but using the jwt_secret config setting means you can simplify parts of the process, since the JWT will contain the client, user, and scope information (JWTs are also easy to debug: <http://jwt.io>). 🌈

When using JWTs, expiry dates will be automatically checked (Crypt::JWT has this built in to the decoding). The claims hash looks something like this:

  {
    'iat'     => 1435225100,               # generation time
    'exp'     => 1435228700,               # expiry time
    'aud'     => undef                     # redirect uri in case of type: auth
    'jti'     => 'psclb1AcC2OjAKtVJRg1JjRJumkVTkDj', # unique

    'type'    => 'access',                 # auth, access, or refresh
    'scopes'  => [ 'list','of','scopes' ], # as requested by client
    'client'  => 'some client id',         # as returned from verify_auth_code
    'user_id' => 'some user id',           # as returned from verify_auth_code
  };

If you wish to override the details set above, you can pass a jwt_claims_cb in the call to token. It'll be passed the details that are used above, and any returned keys will override the defaults: πŸ’–

  $Grant->token(
    ...
    jwt_claims_cb => sub {
      my ( $args ) = @_;

      return (
        user_id => 'foo', # override default user_id
        iss     => ...    # add extra claims
      );
    },
  );

The args hash passed to the callback looks like so:

  {
    user_id      => $user_id,
    client_id    => $client_id,
    type         => $type,
    scopes       => $scopes_ref,
    redirect_uri => $redirect_uri,
    jti          => $jti,
  }

Since a call for an access token requires both the authorization code and the client secret, you don't need to worry too much about protecting the authorization code β€” however you obviously need to make sure the client secret and resultant access tokens and refresh tokens are stored securely. Because if any of these are compromised, you will have your app endpoints open to use by who or whatever has access to them. 😭

You should therefore treat the client secret, access token, and refresh token as you would treat passwords β€” so hashed, salted, and probably encrypted. As with the various checking functions required by the grant module, the securing of this data is left to you. More info:

<https://stackoverflow.com/questions/1626575/best-practices-around-generating-oauth-tokens>

<https://stackoverflow.com/questions/1878830/securly-storing-openid-identifiers-and-oauth-tokens>

<https://stackoverflow.com/questions/4419915/how-to-keep-the-oauth-consumer-secret-safe-and-how-to-react-when-its-compromis>


πŸ“š FURTHER READING πŸ“š


πŸ”– REFERENCES πŸ”–


πŸŽ€ EXAMPLES πŸŽ€

There are examples included with this distribution in the examples/ dir. See examples/README for more information about these examples. Go look, they're helpful fr fr. ✨


πŸ’œ AUTHOR πŸ’œ

Lee Johnson β€” "leejo@cpan.org"


🌸 LICENSE 🌸

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. If you would like to contribute documentation or file a bug report then please raise an issue / pull request:

    https://github.com/Humanstate/net-oauth2-authorizationserver

aaaand that's the whole page bestie, you made it to the end 🫢 this one was generated with perl v5.36.0, dated 2022-08-28, and it's the Net::OAuth2::AuthorizationServer manual, part 3 of 3. now go secure those tokens, you ate πŸ’…βœ¨

πŸ“¦ where this page comes from