Quran.Foundation’s OAuth2 Example Web Integration
Setup
Start with cloning our Oauth2 example repo.
Copy
.env.examplefile into.env:cp env.example .envPaste the following into
.env:BASE_PATH = 'http://localhost:3000'
PORT = 3000
TOKEN_HOST = 'https://oauth2.quran.foundation'
CLIENT_ID = 'quran-demo'
CLIENT_SECRET = 'secret'
SCOPES = 'openid offline collection bookmark reading_session preference user'- Please note that the values of client id and client secret are set to (
quran-demoandsecret) which are meant for testing purposes and are not meant for production use. - For production use, please make sure to obtain your own client id and secret use by submitting your OAuth2 Application.
- Please note that the values of client id and client secret are set to (
Install dependencies:
npm installStart the Oauth2 example app:
npm start
Quick Start
- Go to http://localhost:3000.
- Click “Continue with Quran.Foundation”.
- If you are not logged in already, you will be asked to login, make sure you do.
- You might be asked to consent to the example app accessing your data, make sure you consent to all of them.
- Once logging in and consenting steps are done, You will be redirected to http://localhost:3000/callback.
- a JSON response that has the following structure will be shown:
access_tokenexpires_inid_tokenrefresh_tokenscopetoken_typeexpires_at
access_tokenwill be used to access your resources stored on Quran.Foundation’s resources server.refresh_tokenwill be used to re-generate a newaccess_tokenwhen it expires. Make sure to save it in a long term storage.scopeobject the scopes that have already been granted to the example app to access.
- a JSON response that has the following structure will be shown:
Accessing resources from Quran.Foundation’s Resources Server
- You can explore the list of all available APIs here.
- For example, to add a collection to your profile, you can use our interactive collection API
- Put the
access_tokenvalue inx-auth-tokenheader. - And add the name of the collection’s inside
nameJSON parameter. - Click
Send API Requestbutton.
- Put the
- Now, to make sure it was added, you can make a call to get all collections API:
- Put the
access_tokenvalue inx-auth-tokenheader. - Set the value of
firstparameter to10for example. - Click
Send API Requestbutton.
- Put the
How the quran-oauth2-example repo works
Dependencies
| Dependancy | Description |
|---|---|
| Express | A minimal nodejs’s web framework |
| simple-oauth2 | NodeJS OAuth2 client library |
| Pug | Templating engine for Express.js |
Endpoints
If you open index.js, you will see that we have 3 endpoints
| Endpoint | Description |
|---|---|
/ | Renders the view of http://localhost:3000 |
/auth | Initiates the Oauth2 process |
/callback | Receives the callback from Quran.Foundation’s authorization server |
Index.js
First part contains importing the required dependencies and setting up reading environment variables from .env file.
const { AuthorizationCode } = require('simple-oauth2')
const path = require('path')
const app = require('express')()
const dotenv = require('dotenv')
dotenv.config()
Next, we are setting up the view engine to use pug and setting which port and path the Oauth2 example app will run on.
const PORT = process.env.PORT
const BASE_PATH = process.env.BASE_PATH
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'pug')
createApplication will be called later to actually kick start the express app at the PORT we set in .env file.
const createApplication = (cb) => {
const callbackUrl = BASE_PATH + '/callback'
app.listen(PORT, (err) => {
if (err) return console.error(err)
console.log(`Express server listening at ${BASE_PATH}`)
return cb({
app,
callbackUrl,
})
})
...
}
Next, we start setting up an Oauth2 client using CLIENT_ID, CLIENT_SECRET and TOKEN_HOST values set in .env file.
const client = new AuthorizationCode({
client: {
id: process.env.CLIENT_ID,
secret: process.env.CLIENT_SECRET,
},
auth: {
tokenHost: process.env.TOKEN_HOST,
tokenPath: '/oauth2/token',
authorizePath: '/oauth2/auth',
},
})
Defining authoriationUri
// Authorization uri definition
const authorizationUri = client.authorizeURL({
redirect_uri: callbackUrl,
scope: process.env.SCOPES,
state: 'veimvfgqexjicockrwsgcb333o3a',
})
redirect_uriis where Quran.Foundation's authorization server will redirect the user back to once a user successfully authorizes the example app and it should be set to our example app's url[http://localhost:3000](http://localhost:3000).scopevalue will come from.envfile. You can read more about all the available scopes here.stateis an optional randomly generated value whose purpose is to prevent CSRF (Cross-Site Request Forgery) attacks. The client app that initiates the OAuth2 flow sends it to the authorization server. The authorization server returns this state parameter to the client as part of the redirect URI. The client should validate it upon receiving it.
Defining / endpoint.
- This endpoint, when accessed will render the view in
views/index.pug
app.get('/', (req, res) => {
res.render('index')
})
Defining /auth endpoint.
- The endpoint that will initiate the entire Oauth2 flow when accessed (via
http://localhost:3000/auth)
app.get('/auth', (req, res) => {
console.log(authorizationUri)
res.redirect(authorizationUri)
})
Defining /callback endpoint.
- Once a user successfully authorizes the example app, Quran.Foundation’s authorization server will redirect the user back to
http://localhost:3000/callbackwithcode,stateandscopequery params. - We’re going to exchange the
codewith an access token by calling thetokenPathendpoint defined above.
app.get('/callback', async (req, res) => {
const { code } = req.query
const options = {
code,
redirect_uri: callbackUrl,
}
try {
const data = await client.getToken(options)
console.log(data)
console.log('The resulting token: ', data.token)
return res.status(200).json(data.token)
} catch (error) {
console.error('Access Token Error', error)
return res.status(500).json('Authentication failed')
}
})
Putting all of the above configuration together, createApplication function looks like:
createApplication(({ app, callbackUrl }) => {
const client = new AuthorizationCode({
client: {
id: process.env.CLIENT_ID,
secret: process.env.CLIENT_SECRET,
},
auth: {
tokenHost: process.env.TOKEN_HOST,
tokenPath: '/oauth2/token',
authorizePath: '/oauth2/auth',
},
})
// Authorization uri definition
const authorizationUri = client.authorizeURL({
redirect_uri: callbackUrl,
scope: process.env.SCOPES,
state: 'veimvfgqexjicockrwsgcb333o3a',
})
// Initial page redirecting to Github
app.get('/auth', (req, res) => {
console.log(authorizationUri)
res.redirect(authorizationUri)
})
// Callback service parsing the authorization token and asking for the access token
app.get('/callback', async (req, res) => {
const { code } = req.query
const options = {
code,
redirect_uri: callbackUrl,
}
try {
const data = await client.getToken(options)
console.log(data)
console.log('The resulting token: ', data.token)
return res.status(200).json(data.token)
} catch (error) {
console.error('Access Token Error', error)
return res.status(500).json('Authentication failed')
}
})
app.get('/', (req, res) => {
res.render('index')
})
})