-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Office Hours Khoury OAuth Implementation #683
base: master
Are you sure you want to change the base?
Conversation
@jtavera235 is attempting to deploy a commit to the Sandbox NU Team on Vercel. A member of the Team first needs to authorize it. |
let md = forge.md.sha256.create(); | ||
md.update(codeVal); | ||
const hashedCodeChallenge: string = md.digest().toHex(); | ||
const windowReference = window.open( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to change it whenever we re-visit this, but can this be a string literal?
return ( | ||
<Container> | ||
<ContentContainer> | ||
<h1>You are currently not logged in</h1> | ||
<p>Click the button below to login via Khoury Admin</p> | ||
<Button href="https://admin.khoury.northeastern.edu/teaching/officehourslogin/"> | ||
<Button | ||
onClick={() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i dont think you have to wrap this function in a lambda, you can just pass it in as data
KHOURY_ADMIN_OAUTH_API_URL=https://admin-alpha.khoury.northeastern.edu/api/oauth | ||
KHOURY_ADMIN_OAUTH_URL=https://admin-alpha.khoury.northeastern.edu/oauth | ||
OAUTH_CLIENT_ID=10aac8aac0f1f711756f |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i hope these arent very important (i think the PR comment said something about these being dummy values but I want to make sure lol)
"https://admin.khoury.northeastern.edu/api/oauth"; | ||
export const KHOURY_ADMIN_OAUTH_URL = | ||
"https://admin.khoury.northeastern.edu/oauth"; | ||
export const OAUTH_CLIENT_ID = "<replace_with_khoury_client_id>"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you put a TODO here
Description
This PR implements the whole OAuth flow for the Office Hours app using the new Khoury Admin OAuth server that was supervised by Alex Grob and Alan Mislove. This will be the new way in which users will be able to sign into Office Hours. Currently, this PR is a draft for the following 2 reasons:
Once the Khoury Admin team merges in the OAuth server, the Office hours team will be contacted with credentials that the Office hours team needs to add. These credentials include the client secret, client id, and redirect uri. Currently, there are variables inside the common package's index.ts file that was used as testing but once the Office Hours team has these credentials, they must remove those values from the common package and add it to the application's environment variable. They must also change every instance that uses these variables to now point to the values inside the environment variables. This is mainly inside the login.tsx file and the login.controller.ts file.
If there are any code changes required that does not change the OAuth logic, then the Office Hours team is free to make those changes as needed. As my co-op will about to end, I will be inactive for the rest of the Summer term but if the Office Hours team has any questions regarding the OAuth logic or general questions do not hesitate to reach out to me via Microsoft Teams. Any general questions you may have may also be answered by Alex Grob. The plan is to have OAuth be the default login path starting in the Fall semester. It is HIGHLY recommended to read the below summary as to how OAuth works before jumping into the codebase. This is the documentation that new Khoury OAuth clients will need to follow. This PR implements everything for the Office Hours app but is still encouraged to read and understand.
Khoury Admin OAuth2 Implementation
How it works
At a high level, Khoury Admin's OAuth2 implementation works by registering clients, which are applications that wish to provide an option to sign in with Khoury Admin, and giving users the ability to see whether they wish to connect their Khoury Admin account, and then providing the clients the ability to make API requests to access a user's resources. For security reasons, clients are given certain permissions (i.e. scope) and they can only access the resources based on their permissions. This is just a high level overview of OAuth2 and will make more sense in a little bit as more details is provided below.
The Khoury Admin OAuth2 implementation was built following the OAuth2 protocol. For a general understanding as to how OAuth2 works this link contains great resources that explains it very well. (https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2)
Khoury Admin OAuth2 General Flow
TOAuth starts by a client registering for an account. Their account will contain the following information:
An
organization name
is just the name of the client, such as GraduateNU. Theclient ID
is one of the identifiers for a client, it is generally okay if it is public but it's meant to identify the client is certain API requests. Theclient secret
is another identifier for the client but should be kept secret and is only used when a client is requesting JWT Access tokens. Ascope
translates to a list of a client's permission. The different scope types areuser.info
,student.info
,ta.info
,student.courses
,instructor.courses
.user.info
scope represents being able to access the current user's email, name, photo url, and the account type (such as student, faculty, staff, or advisor)student.info
scope represents being able to access a students major, past courses (transcript), NUID, and catalog yearta.info
scope represents being able to access if the current user is a ta.info and which course they are ta.info'ing forstudent.courses
scope represents being able to access the current courses the current user is registered ininstructor.courses
scope represents being able to access the current courses the current user is instructing if they are an instructorThere will be many instances when a client's scope has to be passed into the request url. However, a client contains a list of scopes, so to pass in the scopes to the URL the scopes will go in the following format:
/&scopes=<First scope>&scopes=<Second Scope>
etc. Let's say a client has theuser.info
andta.info
scopes. Those scopes will be passed in like<request_url>/&scopes=user.info&scopes=ta.info
. If a client were to haveuser.info
,ta.info
, andstudent.info
, then the route would look like<request_url>/&scopes=user.info&scopes=ta.info&scopes=student.info
. TheRedirect URI
refers to the full URL the OAuth login screen will redirect a user to after they successfully login and accepted the permissions. This would generally be an account's home page or the screen that an application would take a user after logging in. The redirect uri must begin withhttps://<uri>
or else the redirection will not work properly. It is important that a client saves all their account information in a secure and reliable way, especially a client's ID and secret. A Khoury admin will contact the client with their client secret, client id, and list of scopes once their account has been created.After a client has an account with the OAuth provider, the client must then provide the option to log a user in with their OAuth provider account. This would generally be done with a button that links to the OAuth provider's login page. The URL for the login page is
https://admin.khoury.northeastern.edu/oauth/login
. In our case a client would have something on their page that looks like this:It is critically important that a client opens the link with
window.open
and not an<a/>
tag or another linking mechanism. Theresponse_type
parameter is mandatory and has to contain the valuecode
as shown above. This indicates to the OAuth provider that the client will be using the authorization code method of OAuth2 authentication.challenge
portion of the URL. This is part of a security mechanism of OAuth 2.0 called PKCE. Essentially, the client is suppose to generate a sufficiently random value as well as a SHA-256 hash of this value. The client then passes the hash value into the URL with the parameter namedchallenge
. The client then needs to store the original value somewhere to pass in a later request. A good place to store the original value would be a browser's local storage. It is recommended that clients use thenode-forge
library to create the SHA-256 values. Below is a recommended example as to how to implement this in javascript:state
portion of the URL. This is part of a security mechanism of OAuth 2.0 meant to protect against CRSF attacks. Essentially, the client is suppose to generate a sufficiently large value (at least 10 characters/numbers) and store it somewhere (local storage) for verification. When the provider returns the authorization code, after a client successfully logs in, it will also return a state value. The client is in charge of comparing the state value the client has stored and the state value that the provider returns. If these values are different, the client should terminate the OAuth flow and NOT proceed as a CRSF attack may be happening. Usually these values will remain the same but as a client it is very important to check.If a user accepts the clients permissions, they will be redirected back to the client's indicated redirect uri with an authorization code and the same state that was initially passed into the login page from the client. This code, called the authorization code, does not signify a JWT but rather is a way for a client to request JWT access and refresh tokens for the current user. As an example, say a client's redirect uri is
https://google.com
. If a user logs in successfully and accepts the clients scope, they will be redirected back tohttps://www.google.com/?code=<authorization_code>&state=<state>
. It is now the client's responsibility to verify the state value is the same as the state value they have stored and get the authorization code from the request parameters and request the access and refresh tokens. Once a client has received and extracted the authorization code, a client has to make aPOST
request to/api/oauth/token?client_id=<client_id>&client_secret=<client_secret>&grant_type=authorization_code&redirect_uri=<redirect_uri>&verifier=<verifier>code=<auth_code>&scopes=<scope1>
Notice the
verifier
field which represents the original value of the challenge value passed in earlier. This gets passed in the request and the provder will verify that the SHA-256 hash of this random value is the same as the code_challenge field of the authorization code saved in the database.Notice the
grant_type
field which indicates that this request is an authorization request and its value has to be equal toauthorization_code
. When this request is made, the provider will verify that the information is correct and on success it will return a json response that looks like:This contains both the access token and refresh tokens which can be used to access resources. The access token must be put on the
Authorization
request header with the valueBearer <access_token>
Once the valid access token is in the header, the client is able to make API requests to the allowed endpoints and not receieve an authorization/authentication error. A recommendation would be that the process of getting an authorization code, requesting the access token, and using the access token to request resources should be done in an intermediate component right before the component that laods the page after logging in appears. For example, a client would have the Login component which allows the option to sign in with a provider. This takes a user to the prover's login page where they log in and accept/deny permissions. Then, the provider will redirect the user back to the intermediate component where the client processes the authorization code and requests the tokens with it. Upon successfully receiving the tokens, the client can then redirect a user to the post-login component where the client can use a user's tokens to request resources. The intermediate component
Since the client has a user's access and refresh tokens, the client can now create a session for the user and can keep using the tokens to continously request resources and keep a user logged in if a client desires to.
The access token has a lifespan of 12 hours in which afterwards it will expire and the client needs to get new access tokens. To get a new access token for the user, the client needs to make a
POST
request tohttps://admin.khoury.northeastern.edu/api/oauth/token/refresh?client_id=<client_id>&client_secret=<client_secret>&refresh_token=<refresh_token>&grant_type=authorization_code&scopes=<list of scopes>
Notice how this request also needs the
grant_type
parameter and make sure that the value is set torefresh_token
If this request is successful, the response will be a json object that looks like:
A client can continously use the refresh tokens to get new access tokens until the refresh token expires. The refresh token has a lifetime of 3 months (one semester) after which a user will have to log in again.
Client OAuth API Routes
These are the following routes that clients will have access to access their resources:
/api/oauth/userdata/read/
returns the current logged in user's name, email, account type, and photo url/api/oauth/ta/read/
returns whether the current logged in user is a TA and for what course they TA for/api/oauth/studentcourses/read/
returns the current courses the logged in user is enrolled in/api/oauth/instructorcourses/read/
returns the current courses the logged in user is teaching/api/oauth/studentdetails/read/
returns the current user's student information such as major, catalog year, and past coursesSummary
window.open
Authorization
header and can now make requests to access it's allowed resourcesType of change
Please delete options that are not relevant.
yarn install
(node-forge needs to be installed onto the application)How Has This Been Tested?
Please describe how you tested this PR (both manually and with tests)
Provide instructions so we can reproduce.
Checklist: