Avenue Code Snippets

Getting Started with OAuth 2.0 Authorization Code with PKCE

Written by Carlos Lazarin | 8/18/21 5:00 PM

What are OAuth 2.0 roles, grant types, and authorization flows, and how do they work?  Check out these basic concepts and start coding your own application!

What is OAuth 2.0?

OAuth 2.0 authorization framework is a protocol created to provide simple authorization flows for web, mobile, and desktop applications. It replaces the obsolete OAuth 1.0 protocol specified by RFC 5849. 

In summary, OAuth 2.0 allows a user to grant a third-party website or application access to protected resources without revealing their long-term credentials or even their identity, delegating user authentication to the service hosting the user's account, such as Google, Facebook, or AWS Cognito. 

This article aims to provide a basic overview of OAuth 2.0 roles, grant types, and authorization flows. At the end of this snippet, we will start coding an Angular 11 single-page application that uses Authorization Code Flow with PKCE, AWS Cognito, and AWS Amplify, with Spring Boot as the resource server.

Authorization vs Authentication

First, let’s clarify two very important concepts:

  • Authentication: is the process used to identify who the user is;
  • Authorization: is the process used to verify whether the user has access to a particular resource.

Let’s consider a practical analogy for OAuth 2.0 protocol: Imagine you're traveling and you go to check in at a hotel. The hotel’s receptionist will register you as a hotel guest; at this moment, you are authenticated, but you still do not have access to a room. Once the employee provides you with a card to your room door, you possess the "access token." In other words, you are authorized to access the hotel's facilities, your room, and the floor on which it is located (this would be your token scope), until the key is revoked (or the token expires). This is a high level analogy for how OAuth 2.0 works.  

OAuth 2.0 Roles

The OAuth 2.0 protocol defines a few roles:

  • Resource owner: the end user who authorizes a client to access their account data; resource owners can authorize or deny an applicant access to their account data based on the requested scope (i.e. read or write), and this is usually done via a consent screen;
  • Client: if we think about a web application, the client is the browser; basically, the client will interact with the resource owner and request access to its resources;
  • Resource server: a web API exposed over the internet; in order to be able to access protected endpoints, clients need to possess a token. 
  • Authorization server: verifies the user’s identity, authenticates it, and provides it with access tokens. Authorization servers have user credentials, such as the username, password, and respective grants. Some examples of authorization servers are Okta, Auth0, and AWS Cognito. In addition, we might have an API that fulfills both resource and authorization server roles.   
Generic OAuth 2.0 Flow

The steps below summarize the sequence of events to acquire access and consume a resource using the OAuth 2 protocol:

  1. The client requests authorization from the resource owner to access services/resources on his or her behalf; 
  2. If the user authorizes the request, the client will posses an authorization grant;
  3. The client requests an access token from the authorization server using the provided authorization grant;
  4. If the provided identity is authenticated and valid, the authorization server issues an access token to the client;
  5. With a valid access token, the client can call the resource server and, using the acquired token, consume its resources.
OAuth 2.0 Grant Types

OAuth 2.0 grant type is, to put it briefly, a way the client application receives the access token. Authorization grants can be differentiated based on the client type and their trust level:

  • Authorization code: this is the most popular flow and is suitable for web applications executing in a server. In this flow, the client can request the access token and refresh tokens which would be passed to the application web server without passing through the user’s web browser;
    • Authorization Code Flow with Proof Key for Code Exchange (PKCE): PKCE is the recommended flow for single-page applications (JavaScript-based apps) that need an access token. In this flow, neither the access token nor the client secret (a private and encrypted key that a client must have to start the authentication process) are exposed in the client. This flow is also recommended for native/mobile applications;
  • Implicit: this was used for single-page applications for a long time, but today it isn't considered safe since the client secret is available in the application source code through the web browser. Therefore, PKCE is much more highly recommended;
  • Resource Owner Password Credentials: this option is more suitable for scenarios in which the client is absolutely trusted and an iterative form is provided for authentication; this flow should be used only when a redirect-based flow is not possible;
  • Client Credentials: this grant type is more suitable for scenarios in which the client and resource owner are the same (i.e. a cron job that uses a specific API); in this instance, the the identity provider provides a client id and a corresponding client secret for each client registered.
Authorization Code with PKCE Overview 

This flow uses a secret key (called code verifier) created by the client application and later verified by the authorization server. The client application also creates a way to transform the respective code verifier (this is called a code challenge) and sends it together with the verifier over HTTP in order to receive the authorization code. If an attacker tries to intercept the request and steal the authentication data, it will have access only to the authorization code and not be able to exchange it for a token, because the attacker must also possess the code verifier. 

Authorization code with PCKE flow steps are better covered below:

  1. User clicks the login button;
  2. Client creates a random cryptographic code_verifier and generates a code_challenge;
  3. Client calls the authorization server “/authorize” endpoint with a respective code_challenge. A sample call to AWS Cognito would look like this:
GET https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/authorize?

response_type=code&

client_id=ad398u21ijw3s9w3939&

redirect_uri=https://YOUR_APP/redirect_uri&

state=STATE&

scope=aws.cognito.signin.user.admin&

code_challenge_method=S256&

code_challenge=CODE_CHALLENGE
  1. Authorization server redirects user to login screen;
  2. User authenticates and accepts required permissions listed in the consent screen;
  3. Authorization server stores code_challenge and redirects the user back to the client with an authorization code;
  4. Client sends previously received authorization code and code_verifier (created in the step 2) to authorization server in the “/oauth/token” endpoint;
  5. Authorization server verifies code_challenge and code_verifier; if everything is right, it replies back with an id token and the access token;
  6. The client can use the access token now to call the resource server (API) and also get user details.
How Do I Start? 

In the next section, we'll present an example of how to create a solution that uses OAuth2 and Authorization Code with PKCE flow. 

The technical stack we will be working is:

  1. Single-page application using Angular 11;
  2. AWS Amplify (a pack of tools and services created by Amazon in order to simplify and expedite the development of scalable full stack applications);
  3. Resource Server with Spring Boot 2.4.2 and Java 11;
  4. AWS Cognito as Authorization Server (Amazon’s documentation states that they support Authorization Code with PKCE).

Initial requirements include:

  1. NodeJS and Angular CLI installed on your machine;
  2. An AWS account (for new users, Amazon also provides a free tier that can be used)
Creating a Client Application (Angular 11 Single-Page Application)

First, we will create our client application with Angular 11:

1. Open a terminal window and type the command below to create a new Angular application:

> ng new my-oauth2-app

> cd my-oauth2-app

> ng serve
2. After serving it, make sure it is working by accessing http://localhost:4200/
3. Now, we will setup and configure AWS Amplify:
           a. Open a new terminal window and type the following command:
npm install -g @aws-amplify/cli
                    i. Configure AWS Amplify running the following command:
amplify configure

                    ii. Select your AWS region;

                    iii. Select your AWS profile name;

                    iv. When requested, add AdministratorAccess to your user (do not forget to take notes for your newly created accessKeyId and secretAccessKey);

                    v. Go back to the terminal window and complete the next configuration steps;

                    vi. Congratulations, we just setup AWS Amplify!

4. Go back to your Angular code and change src/polyfills.ts file:
(window as any).global = window;

(window as any).process = {

  env: { DEBUG: undefined },

};
5. Now it is time to install Amplify libraries in our Angular App:
npm install --save aws-amplify @aws-amplify/ui-angular
6. Let’s check to see if there are errors with our application: 
          a. In a terminal window, inside our Angular application folder, type: 
npm start

          b. Now access http://localhost:4200 and check that everything is fine;

7. Go to our Angular application source code:

          a. Open src/app/app.module.ts file and add Amplify UI Angular module:

import { AmplifyUIAngularModule } from '@aws-amplify/ui-angular';
...imports: [

    BrowserModule,

    AppRoutingModule,

    /* configure app with AmplifyUIAngularModule */

    AmplifyUIAngularModule

  ],

...
8. Initializing AWS Amplify:
          a. In the terminal window, inside our application folder, type:
amplify init

          b. The previous command automatically creates a top level directory called “amplify” that stores all of our app capabilities, such as authentication, storage, authorization rules, etc.;
          c. Fill in all required settings:
                    i. Enter the name for your project or keep the suggested default name;
                    ii. Enter environment name - in this case “dev” for now;
                    iii. Choose your default editor;
                    iv. Choose the type of app you are creating - JavaScript;
                    v. Select JavaScript framework type - angular;
                    vi. Select the source directory path: src
                    vii. Select the distribution directory path: dist/my-ouath2-app
                    viii. Build the command: npm run-script build
                    ix. Start the command: ng serve

          d. Amazon profile: choose the same one created when we ran the “amplify configure” command;

9. Now, it’s time to use Amplify and configure the application’s authentication process with AWS Cognito:
          a. Create an authentication service:
                    i. Go to a terminal, enter your app folder, and type:
amplify add auth

                    ii. When asked:

                             1. Do you want to use the default authentication and security configuration? Default configuration
                             2. How do you want users to be able to sign in? Email
                             3. Do you want to configure advanced settings? No, I am done.

                    iii. Now deploy your authentication service changes to AWS:

                             1.  Go to the terminal again and type: 

amplify push
                             2. When asked, confirm the environment choice;
                             3. In order to double check your changes, go back to the terminal and type:
amplify console

10. Finally, let’s add login UI to our application; AWS Amplify has an authentication UI component we can use that will provide the entire authentication flow and, consequently, it saves us some time: 

11. Add the amplify-authenticator component to the top of src/app/app.component.html file:

<amplify-authenticator>

  <div>

    OAuth 2 Sample App

    <amplify-sign-out></amplify-sign-out>

  </div>

</amplify-authenticator>

12. Finally, serve the application and enjoy it. AWS Amplify has a lot of customization options for its authenticator component. You also might want to use your own login screen, and this option is available too; feel free to take a look at their documentation for more details. 

Summary of Learnings

In this article, we covered key concepts about OAuth 2.0 and Authorization Code with PCKE grant flow. We also started creating a full stack application with Angular 11, AWS Cognito, and Amplify.

Feel free to share comments and questions about your experience with this process below!

 

References:
  1. RFC-6749 documentation. Accessed 1.18.2021 

  2. OAuth 2.0 .Net. Accessed 1.18.2021  

  3. Digital Ocean Community. Accessed 1.18.2021  

  4. Google Developers. Accessed 1.18.2021  

  5. OpenId .Net.  Accessed 1.18.2021  

  6. Amazon Cognito. Accessed 1.18.2021   

  7. Angular CLI. Accessed 1.18.2021  

  8. Amazon Amplify. Accessed 1.18.2021