___ _ _ _ ____ ___
/ _ \ / \ _ _| |_| |__ |___ \ / _ \
| | | |/ _ \| | | | __| '_ \ __) || | | |
| |_| / ___ \ |_| | |_| | | | / __/ | |_| |
\___/_/ \_\__,_|\__|_| |_| |_____(_)___/
_____ _
| ____|_ ____ _ _ __ ___ _ __ | | ___
| _| \ \/ / _` | '_ ` _ \| '_ \| |/ _ \
| |___ > < (_| | | | | | | |_) | | __/
|_____/_/\_\__,_|_| |_| |_| .__/|_|\___|
|_|
- Installation and Setup
- Important Links
- Oauth2.0 Things to Know
- Flows
- Authorization Code Grant
- Refresh Token
- Database
- URL Queries
- Clone this Repo
cd
into the project root folder, and runyarn
- If
yarn
is not installed, install it and then runyarn
- If
- Run
yarn authServer
to boot up the oauth 2.0 server - Run
yarn devAuth
to boot up the oauth 2.0 server in dev mode. This will enable hot reloading when your code changes. - Run
yarn test
to run unit tests that cover all implemented grants- For verbose output, modify
level
inauth/tests/setup.js
to beDebugControl.levels.ALL
- For verbose output, modify
Checkout Oauth-server-github if you are running into any weird errors. Tutorials are seriously lacking on this implementation of the Oauth2.0 protocol as most people use an external service for it. Luckily, the errors are pretty specific, so you can go through their code to figure out what is happening.
Also, if you want to see how the middleware is generated, checkout this to see the middleware stuff. Their examples are out of date, so ignore them.
OAuth has a few parameters that are important to understand. Here is a list of good things to know:
State is an optional string provided by the client. It helps the client to protect against Cross Forgery requests and should not be used to transmit private data as it may be openly exposed and changed.
First, some definitions and reminders:
- Client: The application wanting to have access to your resources
- User: The person wanting to use your resources on the Client
- Authorization: The process of determining whether something has access to protected resources
- Authentication: The process of determining a person's identity.
- OAuth2.0: A protocol outlining how authorization should happen. It is NOT an authentication library. You will need to provide that yourself.
Each of the grants provide a token which enables the user to access resources like the following diagram shows:
- Token is passed up in the authorization header
- Oauth Server validates the token
- Protected Resources are sent back down
Aight, with those out of the way, we need to cover the basic flow with the authorization code grant.
- Authorization
- Client application contacts the Server and requests access
- Client application provides a client_id (unique string identifier)
- Client provides a redirect uri to send the user after the code is delivered
- Client may provide user data for authentication purposes
- Server validates information and sends back an authorization code
- Token
- Client uses received authorization code to request a token
- Client sends client_id, client_secret (if applicable)
- Server validates request, and sends a token.
- Authentication
- Client uses token to gain access to Server's protected resources
In the OAuth2.0 library, each of the above areas are handled within
dedicated urls. Specific details on how to handle the different things
are added to the model
object within the OAuth server.
Now, we will explore each of the above 3 areas in depth.
After hitting the Authorization url, the following calls are made within the model object in this order:
getClient
: This will extract theclient_id
from the request's query or body parameter. You can then go through and verify that the client is indeed a good client, what redirects are permitted for the Client, and what grants they have access to (in this example, just 'authorization_code'). Upon verification, return a valid Client object.- After calling
getClient
, if you passed anauthenticateHandler
to theauthorize
method call, this will then be called. If you return some non-falsy object, that object will be the User and will assume the User was able to authenticate. If you return a falsy object, we will assume that the user failed to authenticate, and must be booted. So, in short, this is where you authenticate the user.
- After calling
saveAuthorizationCode
: This will give you an authorization code, the retrieved Client Object fromgetClient
, and the user from theauthenticateHandler
. This information needs to be stored in your database. Once stored, return the information
After making the above calls, the server redirects you to the provided redirect_uri
with the authorization code present as a url query parameter.
After hitting the token url, the following calls are made within the model object in this order:
getClient
: Same as before, but will now allow you set theclient_secret
to ensure the client is a valid client.getAuthorizationCode
: using theauthorizationCode
the client provides, look up in the database for the client, user, and code information for that code, and return it. If none, return false to stop the request as it is invalid.revokeAuthorizationCode
: using theauthorizationCode
the client provides, delete from the database where the code exists. Return true if the code was deleted, false otherwise. Each authorization code can only be used once.generateAccessToken (optional)
: using the client and user provided, generate an access token. If not provided, the library will use its in-built library to generate a token. If you want to use JWTs, this is where that would happen.saveToken
: using the client, code, and token generated earlier, save this information in the database.
The token is then sent as a json response like this:
{
access_token: "38a72b9262f931a74377dc4f8c0d1d906a89af35",
token_type: "Bearer",
expires_in: 86399
}
Use the token type and token code to add an authorization header like this: ${token_type $token_code}
. This will allow the token to be transmitted securely.
After hitting an authenticate url, the following calls are made within the model object in this order:
getAccessToken
: using the token code provided by the client, return the token, client, and user associated with the token code. If not valid, return false.
If you want to access this information in your routes, it is found in res.locals.oauth.token
, so you immediately have access to the client and user information associated with the token.
The refresh token flow is one of the simplest of the grants. After any successful grant flow is completed and a token is generated, a refresh token is created along-side. If the refresh token is then returned with the other information, the client will be able to use the refresh_token
with its client_id
, client_secret
, and grant_type
of refresh_token in a post to the /token route to get access to a new valid token.
There are four tables that should be stored in the database:
- Client
- User
- Authorization Code
- Token
The server will make use of the stored value in these for authorization and authentication purposes. Ideally, the database system used would be promise-based such that they can just be returned. If this is not available, you can make use of the callback parameter in each of the model functions.
This stores information on the client.
- id: unsigned long primary key auto_increment. // For Relations
- client_id: String unique // Client knows
- client_secret: String // Client knows
- data_uris: [String] // Array of acceptable redirect locations
- grants: [String] // Array of grants client has access to (authorization_code being one)
This stores information about the user.
- id: unsigned long primary key auto_increment. // For Relations
- Anything else you want / need for your server
This stores information related to the authorization code
- authorization_code: String primary key // string with a valid code
- expires_at: Date // When the code expires
- redirect_uri: String // String with a valid uri
- client_id: unsigned long references Client(id)
- user_id: unsigned long references User(id)
This stores information related to your tokens
- access_token: String primary key // string with a valid access token
- access_token_expires_at: Date // When token expires
- client_id: unsigned long references Client(id)
- user_id: unsigned long references User(id)
Once everything is set up the way you want it, you are ready to start making requests to the server. As a reminder, there are three categories of requests that are possible:
- Get Authorization Code
- Get Token
- Get Access to Protected Resource
This section will outline how each of these requests ought to be formatted to successfully go through.
The request for an authorization code requires the following information:
- client_id // The unique string identifying a client
- redirect_uri // The place to redirect after receiving the code
- response_type // what the client is expecting. Should be
code
These parameters can be included within the body of a POST request, or be sent as URL Query Parameters like this: /request/authorization?client_id=<ID>&redirect_uri=<URL>&response_type=code
You can additionally send up other information to help validate the user within the authentication handler.
You know this has handled things successfully when you redirect to the uri you provided.
The request for an access token requires the following information:
- client_id // Unique string of client
- client_secret (if applicable) // client secret key
- grant_type // authorization_code in this example
The request should additionally have the following header:
'Content-Type': 'application/x-www-form-urlencoded'
and the data should be provided within the body of a post request.
This will send back a json response as outlined earlier.
Requesting access to protected resources consists of making the request as usual, but adding the following header:
{
Authorization: `${tokenType} ${token}`,
}
with the tokenType and token coming from the json response in the token request.