├── .vscode └── settings.json ├── DiscordStrategy ├── Discord_Auth.ts ├── Discord_Auth_test.ts ├── Discord_client.ts └── Discord_client_test.ts ├── GitHubStrategy ├── GitHub_Auth.ts ├── GitHub_Auth_test.ts ├── GitHub_client.ts └── GitHub_client_test.ts ├── GoogleStrategy ├── Google_Auth.ts ├── Google_Auth_test.ts ├── Google_client.ts └── Google_client_test.ts ├── LICENSE ├── LinkedInStrategy ├── linkedIn_Auth.ts ├── linkedIn_Auth_test.ts ├── linkedIn_client.ts └── linkedIn_client_test.ts ├── README.md ├── SpotifyStrategy ├── spotify_Auth.ts ├── spotify_Auth_test.ts ├── spotify_client.ts └── spotify_client_test.ts ├── examples ├── Abc.ts ├── Oak.ts ├── Opine.ts └── Pogo.ts └── mod.ts /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": true 5 | } -------------------------------------------------------------------------------- /DiscordStrategy/Discord_Auth.ts: -------------------------------------------------------------------------------- 1 | import { DiscordClient } from './Discord_client.ts'; 2 | 3 | export abstract class DiscordGrant { 4 | constructor( 5 | protected readonly client: DiscordClient 6 | ) {} 7 | } 8 | 9 | export class DiscordStrategy extends DiscordGrant { 10 | constructor( 11 | client: DiscordClient 12 | ) { 13 | super(client); 14 | } 15 | 16 | // SampleLink: string = 'https://discord.com/api/oauth2/authorize?response_type=code&client_id={your_clientId}&scope={your_encoded_scope}&state={state}&redirect_uri={your_redirect_uri}' 17 | 18 | // part 1 of DenOAuth strategy 19 | /** Builds a URI you can redirect a user to to make the authorization request. */ 20 | createLink() { 21 | // The primary reason for using the state parameter is to mitigate CSRF attacks by using a unique and non-guessable value associated with each authentication request about to be initiated. 22 | const state:number = Math.floor(Math.random() * 1000000000); 23 | const encodeLink:string = encodeURIComponent(this.client.config.redirect); 24 | const encodeScope:any = encodeURIComponent(this.client.config.scope) 25 | const SampleLink = `https://discord.com/api/oauth2/authorize?response_type=code&client_id=${this.client.config.clientId}&scope=${encodeScope}&state=${state}&redirect_uri=${encodeLink}`; 26 | return SampleLink; 27 | } 28 | 29 | 30 | // part 2 of DenOAuth strategy 31 | async processAuth(stringPathName:any) { 32 | /** Parses the authorization response request tokens from the authorization server. */ 33 | const code: string = JSON.stringify(stringPathName.search) 34 | const parsedCode:string = code.slice(code.indexOf('"?code=')+7, code.indexOf('&state')) 35 | const userResponse:unknown[] = []; 36 | 37 | /** Exchange the authorization code for an access token */ 38 | await fetch('https://discord.com/api/oauth2/token',{ 39 | method: 'POST', 40 | headers: { 41 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" 42 | }, 43 | body: new URLSearchParams({ 44 | 'grant_type': "authorization_code", 45 | 'code': parsedCode, 46 | 'redirect_uri': this.client.config.redirect, 47 | 'client_id': this.client.config.clientId, 48 | 'client_secret': this.client.config.clientSecret 49 | }) 50 | }) 51 | .then((response: any) => { 52 | console.log(response) 53 | return response.text() 54 | }) 55 | .then( async (paramsString: any) => { 56 | const params = new URLSearchParams(paramsString) 57 | const tokenKey = []; 58 | 59 | for (const [key, value] of params.entries()){ 60 | tokenKey.push(key, value) 61 | } 62 | const obj: any = tokenKey[0] 63 | const values: unknown[] = Object.values(obj) 64 | 65 | const tokenArr: unknown[] = [] 66 | let i = 18; 67 | while (values[i] !== '"') { 68 | tokenArr.push(values[i]) 69 | i++ 70 | } 71 | const bearerToken = tokenArr.join('') 72 | 73 | /** Use the access token to make an authenticated API request */ 74 | await fetch("http://discordapp.com/api/users/@me", { 75 | headers: { 76 | Authorization: `Bearer ${bearerToken}`, 77 | }, 78 | }) 79 | .then(response => response.json()) 80 | .then(data => { 81 | // Returning Discord Response Data in console for Client 82 | console.log(`Discord Response Data: ${data}`) 83 | userResponse.push(data) 84 | }) 85 | .catch(console.error) 86 | }) 87 | return userResponse[0]; 88 | } 89 | } -------------------------------------------------------------------------------- /DiscordStrategy/Discord_Auth_test.ts: -------------------------------------------------------------------------------- 1 | import { DiscordClient } from './Discord_client.ts'; 2 | // import { LinkedInStrategy } from './linkedIn_Auth.ts'; 3 | import { assertEquals } from "https://deno.land/std@0.119.0/testing/asserts.ts" 4 | 5 | Deno.test("Discord's createLink method should return the correct url", () => { 6 | const client = new DiscordClient({ 7 | clientId: '688zz8dnnxjo4t', 8 | clientSecret: 'YHhQQW3BaNQCFilB', 9 | redirect: 'http://localhost:3000/auth/discord/callback', 10 | tokenUri: 'https://discord.com/api/oauth2/token', 11 | scope: 'identify' 12 | }); 13 | 14 | const dummy = client.code.createLink() 15 | const dummyEncode = encodeURIComponent('http://localhost:3000/auth/discord/callback') 16 | const dummyEncodeScope = encodeURIComponent('identify') 17 | const dummyState = dummy.slice(dummy.indexOf('&state') + 7); 18 | 19 | assertEquals( 20 | dummy, 21 | `https://discord.com/api/oauth2/authorize?response_type=code&client_id=688zz8dnnxjo4t&scope=${dummyEncodeScope}&state=${dummyState}&redirect_uri=${dummyEncode}` 22 | ); 23 | }); 24 | 25 | Deno.test("parsedCode should parse the URL correctly", () => { 26 | 27 | const randomizedCode = Math.random().toString(36).substring(2,13); 28 | 29 | const fakeStringPathName = (`?code=${randomizedCode}&state=777578398`) 30 | const code:string = JSON.stringify(fakeStringPathName); 31 | const parsedCodeTest = code.slice(code.indexOf('"?code=')+7, code.indexOf('&state')); 32 | 33 | assertEquals( 34 | randomizedCode, 35 | parsedCodeTest 36 | ) 37 | }); 38 | 39 | -------------------------------------------------------------------------------- /DiscordStrategy/Discord_client.ts: -------------------------------------------------------------------------------- 1 | import { DiscordStrategy } from './Discord_Auth.ts'; 2 | 3 | export interface DiscordClientConfig { 4 | /** The client ID provided by the authorization server. */ 5 | clientId:string; 6 | /** The client secret provided by the authorization server, if using a confidential client. Best practice to always keep secret in env file. */ 7 | clientSecret:string; 8 | /** The URI of the client's redirection endpoint (sometimes also called callback URI). */ 9 | redirect:string; 10 | /** The URI of the authorization server's token endpoint. */ 11 | tokenUri:string; 12 | 13 | // Our implementation currently only works with scope set to 'identify' 14 | /** Scopes to request with the authorization request. */ 15 | scope: any; 16 | } 17 | 18 | 19 | export class DiscordClient { 20 | // implements all the methods required to complete OAuth process 21 | public code = new DiscordStrategy(this); 22 | 23 | // interface values cannot be changed outside of class 24 | constructor( 25 | public readonly config: Readonly, 26 | ) {} 27 | } -------------------------------------------------------------------------------- /DiscordStrategy/Discord_client_test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'https://deno.land/std@0.119.0/testing/asserts.ts'; 2 | import { DiscordClient } from './Discord_client.ts'; 3 | import { DiscordStrategy } from './Discord_Auth.ts' 4 | 5 | Deno.test("DiscordClient.code is created", () => { 6 | const client = new DiscordClient({ 7 | clientId: "", 8 | clientSecret: "", 9 | redirect: "", 10 | tokenUri: "", 11 | scope: "" 12 | }); 13 | assert(client.code instanceof DiscordStrategy); 14 | }); -------------------------------------------------------------------------------- /GitHubStrategy/GitHub_Auth.ts: -------------------------------------------------------------------------------- 1 | import { GitHubClient } from './GitHub_client.ts'; 2 | 3 | export abstract class GitHubGrant { 4 | constructor( 5 | protected readonly client: GitHubClient 6 | ) {} 7 | } 8 | 9 | export class GitHubStrategy extends GitHubGrant { 10 | constructor( 11 | client: GitHubClient 12 | ) { 13 | super(client); 14 | } 15 | 16 | // SampleLink: String = 'https://github.com/login/oauth/authorize?response_type=code&client_id=${your_clientId}&redirect_uri=${your_encoded_redirect_Link}&state=${foobar}&scope=${your_encoded_scope}' 17 | 18 | // part 1 19 | /** Builds a URI you can redirect a user to to make the authorization request. */ 20 | createLink = () => { 21 | // The primary reason for using the state parameter is to mitigate CSRF attacks by using a unique and non-guessable value associated with each authentication request about to be initiated. 22 | const state: number = Math.floor(Math.random() * 1000000000) 23 | const encodeLink: string = encodeURIComponent(this.client.config.redirect) 24 | const encodeScope: string = encodeURIComponent(this.client.config.scope) 25 | const SampleLink = `https://github.com/login/oauth/authorize?response_type=code&client_id=${this.client.config.clientId}&redirect_uri=${encodeLink}&state=${state}&scope=${encodeScope}` 26 | return SampleLink 27 | } 28 | 29 | 30 | // part 2 31 | async processAuth(stringPathName: any) { 32 | /** Parses the authorization response request tokens from the authorization server. */ 33 | const code:string = JSON.stringify(stringPathName.search); 34 | const parsedCode:string = code.slice(code.indexOf('"?code=')+7, code.indexOf('&state')); 35 | const userResponse:unknown[] = []; 36 | 37 | /** Exchange the authorization code for an access token */ 38 | await fetch('https://github.com/login/oauth/access_token',{ 39 | method: 'POST', 40 | headers: { 41 | 'Accept': 'application/json', 42 | 'Content-Type': 'application/json' 43 | }, 44 | body: JSON.stringify({ 45 | client_id: this.client.config.clientId, //provided by GitHub 46 | client_secret: this.client.config.clientSecret, //provided by GitHub 47 | code: parsedCode, 48 | redirect_uri: this.client.config.redirect //provided by GitHub 49 | }) 50 | }) 51 | .then((response) => { 52 | return response.text() 53 | }) 54 | .then( async (paramsString: any) => { 55 | const params = new URLSearchParams(paramsString) 56 | const tokenKey: unknown[] = []; 57 | for (const [key, value] of params.entries()){ 58 | tokenKey.push(key, value) 59 | } 60 | const obj: any = tokenKey[0] 61 | const values: unknown[] = Object.values(obj) 62 | const tokenArr = [] 63 | let i = 17; 64 | while (values[i] !== '"') { 65 | tokenArr.push(values[i]) 66 | i++ 67 | } 68 | const bearerToken: string = tokenArr.join('') 69 | 70 | /** Use the access token to make an authenticated API request */ 71 | await fetch("https://api.github.com/user", { 72 | headers: { 73 | Authorization: `Bearer ${bearerToken}`, 74 | }, 75 | }) 76 | .then(response => response.json()) 77 | .then(data => { 78 | // Returning Google Response Data in console for Client 79 | console.log(`GitHub Response Data: ${data}`) 80 | userResponse.push(data) 81 | }) 82 | .catch(console.error) 83 | }) 84 | 85 | return userResponse[0]; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /GitHubStrategy/GitHub_Auth_test.ts: -------------------------------------------------------------------------------- 1 | import { GitHubClient } from './GitHub_client.ts'; 2 | // import { GitHubStrategy } from './GitHub_Auth.ts'; 3 | import { assertEquals } from "https://deno.land/std@0.119.0/testing/asserts.ts" 4 | 5 | Deno.test("GitHub's createLink method should return the correct url", () => { 6 | const client = new GitHubClient({ 7 | clientId: '688zz8dnnxjo4t', 8 | clientSecret: 'YHhQQW3BaNQCFilB', 9 | redirect: 'http://localhost:3000/auth/github/callback', 10 | tokenUri: 'https://github.com/login/oauth/access_token', 11 | scope: 'read:user' 12 | }); 13 | 14 | const dummy = client.code.createLink() 15 | const dummyEncode = encodeURIComponent('http://localhost:3000/auth/github/callback') 16 | const dummyScope = encodeURIComponent('read:user') 17 | const dummyState = dummy.slice((dummy.indexOf('&state') + 7), dummy.indexOf('&scope')); 18 | 19 | assertEquals( 20 | dummy, 21 | `https://github.com/login/oauth/authorize?response_type=code&client_id=688zz8dnnxjo4t&redirect_uri=${dummyEncode}&state=${dummyState}&scope=${dummyScope}` 22 | ); 23 | }); 24 | 25 | Deno.test("parsedCode should parse the URL correctly", () => { 26 | 27 | const randomizedCode = Math.random().toString(36).substring(2,13); 28 | 29 | const fakeStringPathName = (`?code=${randomizedCode}&state=962380336`) 30 | const code:string = JSON.stringify(fakeStringPathName); 31 | const parsedCodeTest = code.slice(code.indexOf('"?code=')+7, code.indexOf('&state')); 32 | 33 | assertEquals( 34 | randomizedCode, 35 | parsedCodeTest 36 | ) 37 | }); -------------------------------------------------------------------------------- /GitHubStrategy/GitHub_client.ts: -------------------------------------------------------------------------------- 1 | import { GitHubStrategy } from './GitHub_Auth.ts'; 2 | 3 | export interface GitHubClientConfig { 4 | /** The client ID provided by the authorization server. */ 5 | clientId:string; 6 | /** The client secret provided by the authorization server, if using a confidential client. Best practice to always keep secret in env file. */ 7 | clientSecret:string; 8 | /** The URI of the client's redirection endpoint (sometimes also called callback URI). */ 9 | redirect:string; 10 | /** The URI of the authorization server's token endpoint. */ 11 | tokenUri:string; 12 | 13 | // Our implementation currently only works with scope set to 'read:user' 14 | /** Scopes to request with the authorization request. */ 15 | scope: any; 16 | } 17 | 18 | 19 | export class GitHubClient { 20 | // implements all the methods required to complete OAuth process 21 | public code = new GitHubStrategy(this); 22 | 23 | // interface values cannot be changed outside of class 24 | constructor( 25 | public readonly config: Readonly, 26 | ) {} 27 | } -------------------------------------------------------------------------------- /GitHubStrategy/GitHub_client_test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'https://deno.land/std@0.119.0/testing/asserts.ts'; 2 | import { GitHubClient } from './GitHub_client.ts'; 3 | import { GitHubStrategy } from './GitHub_Auth.ts' 4 | 5 | Deno.test("GitHubClient.code is created", () => { 6 | const client = new GitHubClient({ 7 | clientId: "", 8 | clientSecret: "", 9 | redirect: "", 10 | tokenUri: "", 11 | scope: "" 12 | }); 13 | assert(client.code instanceof GitHubStrategy); 14 | }); -------------------------------------------------------------------------------- /GoogleStrategy/Google_Auth.ts: -------------------------------------------------------------------------------- 1 | import { GoogleClient } from './Google_client.ts'; 2 | 3 | export abstract class GoogleGrant { 4 | constructor( 5 | protected readonly client: GoogleClient 6 | ) {} 7 | } 8 | 9 | export class GoogleStrategy extends GoogleGrant { 10 | constructor( 11 | client: GoogleClient 12 | ) { 13 | super(client); 14 | } 15 | 16 | // SampleLink: String = 'https://accounts.google.com/o/oauth2/v2/auth?scope=${https://mail.google.com&access_type=offline&include_granted_scopes=true}&response_type=code&state=${foobar}&redirect_uri=${your_encoded_redirectLink}&client_id=${your_clientId} 17 | 18 | // part 1 of DenOAuth strategy 19 | /** Builds a URI you can redirect a user to to make the authorization request. */ 20 | createLink() { 21 | // The primary reason for using the state parameter is to mitigate CSRF attacks by using a unique and non-guessable value associated with each authentication request about to be initiated. 22 | const state: number = Math.floor(Math.random() * 1000000000) 23 | const encodeLink: string = encodeURIComponent(this.client.config.redirect) 24 | const SampleLink = `https://accounts.google.com/o/oauth2/v2/auth?scope=${this.client.config.scope}&response_type=code&state=${state}&redirect_uri=${encodeLink}&client_id=${this.client.config.clientId}` 25 | return SampleLink 26 | } 27 | 28 | 29 | // part 2 of DenOAuth strategy 30 | async processAuth(stringPathName:any) { 31 | /** Parses the authorization response request tokens from the authorization server. */ 32 | const code: string = JSON.stringify(stringPathName.search) 33 | const parsedCode = code.slice(code.indexOf('"?code=')+24, code.indexOf('&scope')) 34 | const userResponse: unknown[] = []; 35 | 36 | /** Exchange the authorization code for an access token */ 37 | await fetch('https://accounts.google.com/o/oauth2/token',{ 38 | method: 'POST', 39 | headers: { 40 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" 41 | }, 42 | 43 | body: new URLSearchParams({ 44 | 'code': parsedCode, 45 | 'client_id': this.client.config.clientId, //provided by Google 46 | 'client_secret': this.client.config.clientSecret, //provided by Google 47 | 'redirect_uri': this.client.config.redirect, //provided by Google 48 | 'grant_type': "authorization_code", //must be hardcoded exactly 49 | }) 50 | }) 51 | .then((response) => { 52 | return response.text() 53 | }) 54 | .then( async (paramsString:any) => { 55 | const params = new URLSearchParams(paramsString) 56 | const tokenKey = []; 57 | for (const [key, value] of params.entries()){ 58 | 59 | tokenKey.push(key, value) 60 | } 61 | const obj:any = tokenKey[0] 62 | const values: unknown[] = Object.values(obj) 63 | const tokenArr:unknown[] = [] 64 | let i = 21; 65 | while (values[i] !== '"') { 66 | tokenArr.push(values[i]) 67 | i++ 68 | } 69 | const bearerToken: string = tokenArr.join('') 70 | 71 | /** Use the access token to make an authenticated API request */ 72 | await fetch("https://www.googleapis.com/drive/v2/files", { 73 | headers: { 74 | Authorization: `Bearer ${bearerToken}`, 75 | }, 76 | }) 77 | .then(response => response.json()) 78 | .then(data => { 79 | // Returning Google Response Data in console for Client 80 | console.log(`Google Response Data: ${data}`) 81 | userResponse.push(data) 82 | }) 83 | .catch(console.error) 84 | }) 85 | 86 | return userResponse[0]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /GoogleStrategy/Google_Auth_test.ts: -------------------------------------------------------------------------------- 1 | import { GoogleClient } from './Google_client.ts'; 2 | // import { GoogleStrategy } from './Google_Auth.ts'; 3 | import { assertEquals } from "https://deno.land/std@0.119.0/testing/asserts.ts" 4 | 5 | Deno.test("Google's createLink method should return the correct url", () => { 6 | const client = new GoogleClient({ 7 | clientId: '688zz8dnnxjo4t', 8 | clientSecret: 'YHhQQW3BaNQCFilB', 9 | redirect: 'http://localhost:3000/auth/google/callback', 10 | tokenUri: 'https://accounts.google.com/o/oauth2/token', 11 | scope: 'https://mail.google.com&access_type=offline&include_granted_scopes=true' 12 | }); 13 | 14 | const dummy = client.code.createLink() 15 | const dummyEncode = encodeURIComponent('http://localhost:3000/auth/google/callback') 16 | const dummyState = dummy.slice((dummy.indexOf('&state') + 7), dummy.indexOf('&redirect_uri')); 17 | 18 | assertEquals( 19 | dummy, 20 | `https://accounts.google.com/o/oauth2/v2/auth?scope=https://mail.google.com&access_type=offline&include_granted_scopes=true&response_type=code&state=${dummyState}&redirect_uri=${dummyEncode}&client_id=688zz8dnnxjo4t` 21 | ); 22 | }); 23 | 24 | Deno.test("parsedCode should parse the URL correctly", () => { 25 | 26 | const randomizedCode = Math.random().toString(36).substring(2,13); 27 | 28 | const fakeStringPathName = (`?state=722421332&code=${randomizedCode}&scope=https://mail.google.com/`) 29 | const code:string = JSON.stringify(fakeStringPathName); 30 | const parsedCodeTest = code.slice(code.indexOf('"?code=')+24, code.indexOf('&scope')); 31 | 32 | assertEquals( 33 | randomizedCode, 34 | parsedCodeTest 35 | ) 36 | }); -------------------------------------------------------------------------------- /GoogleStrategy/Google_client.ts: -------------------------------------------------------------------------------- 1 | import { GoogleStrategy } from './Google_Auth.ts'; 2 | 3 | export interface GoogleClientConfig { 4 | /** The client ID provided by the authorization server. */ 5 | clientId:string; 6 | /** The client secret provided by the authorization server, if using a confidential client. Best practice to always keep secret in env file. */ 7 | clientSecret:string; 8 | /** The URI of the client's redirection endpoint (sometimes also called callback URI). */ 9 | redirect:string; 10 | /** The URI of the authorization server's token endpoint. */ 11 | tokenUri:string; 12 | /** Scopes to request with the authorization request. */ 13 | scope: any; 14 | } 15 | 16 | 17 | export class GoogleClient { 18 | // implements all the methods required to complete OAuth process 19 | public code = new GoogleStrategy(this); 20 | 21 | // interface values cannot be changed outside of class 22 | constructor( 23 | public readonly config: Readonly, 24 | ) {} 25 | } -------------------------------------------------------------------------------- /GoogleStrategy/Google_client_test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'https://deno.land/std@0.119.0/testing/asserts.ts'; 2 | import { GoogleClient } from './Google_client.ts'; 3 | import { GoogleStrategy } from './Google_Auth.ts' 4 | 5 | Deno.test("GoogleClient.code is created", () => { 6 | const client = new GoogleClient({ 7 | clientId: "", 8 | clientSecret: "", 9 | redirect: "", 10 | tokenUri: "", 11 | scope: "" 12 | }); 13 | assert(client.code instanceof GoogleStrategy); 14 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021. All contributers. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LinkedInStrategy/linkedIn_Auth.ts: -------------------------------------------------------------------------------- 1 | import { LinkedInClient } from './linkedIn_client.ts'; 2 | 3 | export abstract class LinkedInGrant { 4 | constructor( 5 | protected readonly client: LinkedInClient 6 | ) {} 7 | } 8 | 9 | export class LinkedInStrategy extends LinkedInGrant { 10 | constructor( 11 | client: LinkedInClient 12 | ) { 13 | super(client); 14 | } 15 | 16 | // SampleLink: string = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={your_client_id}&redirect_uri={your_callback_url}&state=foobar&scope=r_liteprofile%20r_emailaddress%20w_member_social` 17 | 18 | // part 1 of DenOAuth strategy 19 | /** Builds a URI you can redirect a user to to make the authorization request. */ 20 | createLink() { 21 | // The primary reason for using the state parameter is to mitigate CSRF attacks by using a unique and non-guessable value associated with each authentication request about to be initiated. 22 | const state:number = Math.floor(Math.random() * 1000000000); 23 | const encode:string = encodeURIComponent(this.client.config.redirect); 24 | const SampleLink = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${this.client.config.clientId}&redirect_uri=${encode}&state=${state}&scope=${this.client.config.scope}`; 25 | return SampleLink; 26 | } 27 | 28 | 29 | // part 2 of DenOAuth strategy 30 | async processAuth(stringPathName:any) { 31 | /** Parses the authorization response request tokens from the authorization server. */ 32 | const code:string = JSON.stringify(stringPathName.search); 33 | const parsedCode:string = code.slice(code.indexOf('"?code=')+7, code.indexOf('&state')); 34 | const userResponse:unknown[] = []; 35 | 36 | /** Exchange the authorization code for an access token */ 37 | await fetch('https://www.linkedin.com/oauth/v2/accessToken',{ 38 | method: 'POST', 39 | headers: { 40 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" 41 | }, 42 | body: new URLSearchParams({ 43 | 'grant_type': "authorization_code", // hard code 44 | 'code': parsedCode, // helper function 45 | 'redirect_uri': this.client.config.redirect, // linkedin uri 46 | 'client_id': this.client.config.clientId, // provided by linkedin 47 | 'client_secret': this.client.config.clientSecret //provided by linkedin 48 | }) 49 | }) 50 | .then((response) => { 51 | return response.text() 52 | }) 53 | .then( async (paramsString: any) => { 54 | const params = new URLSearchParams(paramsString); 55 | const tokenKey = []; 56 | for (const [key, value] of params.entries()){ 57 | tokenKey.push(key, value) 58 | } 59 | 60 | const obj:any = tokenKey[0]; 61 | const values: unknown[] = Object.values(obj); 62 | const tokenArr: unknown[] = [] 63 | let i = 17; 64 | while (values[i] !== '"') { 65 | tokenArr.push(values[i]) 66 | i++ 67 | } 68 | const bearerToken = await tokenArr.join(''); 69 | 70 | /** Use the access token to make an authenticated API request */ 71 | await fetch("https://api.linkedin.com/v2/me", { 72 | headers: { 73 | Authorization: `Bearer ${bearerToken}`, 74 | }, 75 | }) 76 | .then(response => response.json()) 77 | .then(data => { 78 | // Returning LinkedIn Response Data in console for Client 79 | console.log(`LinkedIn Response Data: ${data}`) 80 | userResponse.push(data) 81 | }) 82 | .catch(console.error) 83 | }) 84 | return userResponse[0]; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /LinkedInStrategy/linkedIn_Auth_test.ts: -------------------------------------------------------------------------------- 1 | import { LinkedInClient } from './linkedIn_client.ts'; 2 | // import { LinkedInStrategy } from './linkedIn_Auth.ts'; 3 | import { assertEquals } from "https://deno.land/std@0.119.0/testing/asserts.ts" 4 | 5 | Deno.test("LinkedIn's createLink method should return the correct url", () => { 6 | const client = new LinkedInClient({ 7 | clientId: '688zz8dnnxjo4t', 8 | clientSecret: 'YHhQQW3BaNQCFilB', 9 | redirect: 'http://localhost:3000/auth/linkedin/callback', 10 | tokenUri: 'https://api.linkedin.com/v2/me', 11 | scope: 'r_liteprofile' 12 | }); 13 | 14 | const dummy = client.code.createLink() 15 | const dummyEncode = encodeURIComponent('http://localhost:3000/auth/linkedin/callback') 16 | const dummyState = dummy.slice((dummy.indexOf('&state') + 7), -20); 17 | 18 | assertEquals( 19 | dummy, 20 | `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=688zz8dnnxjo4t&redirect_uri=${dummyEncode}&state=${dummyState}&scope=r_liteprofile` 21 | ); 22 | }); 23 | 24 | Deno.test("parsedCode should parse the URL correctly", () => { 25 | 26 | const randomizedCode = Math.random().toString(36).substring(2,13); 27 | 28 | const fakeStringPathName = (`?code=${randomizedCode}&state=777578398`) 29 | const code:string = JSON.stringify(fakeStringPathName); 30 | const parsedCodeTest = code.slice(code.indexOf('"?code=')+7, code.indexOf('&state')); 31 | 32 | assertEquals( 33 | randomizedCode, 34 | parsedCodeTest 35 | ) 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /LinkedInStrategy/linkedIn_client.ts: -------------------------------------------------------------------------------- 1 | import { LinkedInStrategy } from './linkedIn_Auth.ts'; 2 | 3 | export interface LinkedInClientConfig { 4 | /** The client ID provided by the authorization server. */ 5 | clientId:string; 6 | /** The client secret provided by the authorization server, if using a confidential client. Best practice to always keep secret in env file. */ 7 | clientSecret:string; 8 | /** The URI of the client's redirection endpoint (sometimes also called callback URI). */ 9 | redirect:string; 10 | /** The URI of the authorization server's token endpoint. */ 11 | tokenUri:string; 12 | 13 | // Our implementation currently only works with scope set to 'r_liteprofile' 14 | /** Scopes to request with the authorization request. */ 15 | scope: string | string[]; 16 | } 17 | 18 | 19 | export class LinkedInClient { 20 | // implements all the methods required to complete OAuth process 21 | public code = new LinkedInStrategy(this); 22 | 23 | // interface values cannot be changed outside of class 24 | constructor( 25 | public readonly config: Readonly, 26 | ) {} 27 | } -------------------------------------------------------------------------------- /LinkedInStrategy/linkedIn_client_test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'https://deno.land/std@0.119.0/testing/asserts.ts'; 2 | import { LinkedInClient } from './linkedIn_client.ts'; 3 | import { LinkedInStrategy } from './linkedIn_Auth.ts' 4 | 5 | Deno.test("LinkedInClient.code is created", () => { 6 | const client = new LinkedInClient({ 7 | clientId: "", 8 | clientSecret: "", 9 | redirect: "", 10 | tokenUri: "", 11 | scope: "" 12 | }); 13 | assert(client.code instanceof LinkedInStrategy); 14 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DenOAuth 2 | 3 | A library for implementing authentication through third party APIs in Deno applications. 4 | DenOAuth attempts to create middleware that can be utilized in any type of application without reliance on other third party modules. It is currently available with apps running on Oak, Abc, Pogo and Opine application frameworks. 5 | 6 | DenOAuth was inspired by js-client-oauth2 7 | 8 | # v1.0.6 9 | 10 | 11 | Currently DenOAuth supports strategies for LinkedIn, GitHub, Google, Spotify, and Discord. 12 | 13 | # Three part strategy 14 | 15 | DenOAuth has a three part process for authorization through each specific api. 16 | 17 | First: Initialize a new object for each client: 18 | 19 | ```ts 20 | const GitHubObject = new GitHubClient({ 21 | clientId: '', 22 | clientSecret: '', 23 | tokenUri: 'https://github.com/login/oauth/access_token', 24 | redirect: 'http://localhost:3000/auth/github/callback', // The redirect uri is added in the GitHub OAuth developer settings 25 | scope: 'read:user', 26 | }); 27 | ``` 28 | 29 | Second: Call our createLink function to redirect the user to enter their credentials for authorization. 30 | 31 | ```ts 32 | ctx.response.body = { 33 | message: 'success', 34 | data: ctx.response.redirect(GitHubObject.code.createLink()) 35 | }; 36 | ``` 37 | 38 | Third: Call our processAuth function, entering the current url as a parameter. This will extract the current code, exchanging the code 39 | for a bearer token. The function then exchanges the token for the information about the user provided from the third party API. 40 | 41 | ```ts 42 | const userProfile = await GitHubObject.code.processAuth(ctx.request.url); 43 | ``` 44 | 45 | # Example with the LinkedIn API using oak 46 | 47 | ```ts 48 | import { Application } from 'https://deno.land/x/oak/mod.ts'; 49 | import { renderFileToString } from 'https://deno.land/x/dejs@0.10.2/mod.ts'; 50 | import { LinkedInClient } from 'https://deno.land/x/denoauth@v1.0.5/mod.ts'; 51 | import { Router } from 'https://deno.land/x/oak/mod.ts'; 52 | 53 | const LinkedInObject = new LinkedInClient({ 54 | clientId: '', 55 | clientSecret: '', 56 | tokenUri: 'https://api.linkedin.com/v2/me', 57 | redirect: 'http://localhost:3000/auth/linkedin/callback', // The redirect uri is added in the LinkedIn OAuth developer settings 58 | scope: 'r_liteprofile', 59 | }); 60 | 61 | const router = new Router(); 62 | 63 | const port: String | any = 3000; 64 | const app = new Application(); 65 | 66 | app.use(router.routes()); 67 | app.use(router.allowedMethods()); 68 | 69 | //Rendering a login page to add login buttons for each third party api 70 | router.get('/login', async (ctx) => { 71 | ctx.response.body = await renderFileToString( 72 | `${Deno.cwd()}/views/login.html`, 73 | {} 74 | ); 75 | }); 76 | 77 | //When clicked, the user is redirected to enter credentials and then redirected to the callback uri 78 | router.get('/linkedin', (ctx) => { 79 | ctx.response.body = { 80 | message: 'success', 81 | data: ctx.response.redirect(LinkedInObject.code.createLink()), 82 | }; 83 | }); 84 | 85 | router.get('/auth/linkedin/callback', async (ctx) => { 86 | // Exchange the authorization code for an access token and exchange token for profile 87 | const userProfile = await LinkedInObject.code.processAuth(ctx.request.url); 88 | 89 | // userProfile is an object of information given by LinkedIn. You can destructure the object to grab specific information 90 | const { localizedFirstName } = userProfile; 91 | 92 | ctx.response.body = `Hello ${localizedFirstName}`; 93 | }); 94 | 95 | console.log(`Server running on port ${port}`); 96 | 97 | await app.listen({ port: +port }); 98 | ``` 99 | 100 | # Stretch Features 101 | 102 | Above is an example of how someone can impliment our middleware into their application, but we still would like to add refresh tokens, 103 | and would like to include many more third party apis. 104 | 105 | # How To Contribute 106 | 107 | We would love to hear your experience and get your feedback on our modules. Feel free to send us any issues, concerns, or suggestions, in our Issues section, or simply contact us through LinkedIn. 108 | 109 | # Developers 110 | 111 | Check out our website here :: [denoauth.org](https://www.denoauth.org) 112 | 113 | Press :: [Medium](https://medium.com/@dannguyen1191/denoauth-solution-deno-oauth-2-0-54d3b6a4ef35) | [Product Hunt](https://www.producthunt.com/posts/denoauth) 114 | 115 | Nick Echols :: [LinkedIn](https://www.linkedin.com/in/nickechols87/) | [GitHub](https://github.com/Nechols87) 116 | 117 | Dan Nguyen :: [LinkedIn](https://www.linkedin.com/in/danlord-nguyen/) | [GitHub](https://github.com/Danlordrises) 118 | 119 | Matt Miller :: [LinkedIn](https://www.linkedin.com/in/matthew-miller2020/) | [GitHub](https://github.com/matthewjohnmiller2020) 120 | 121 | Max Lee :: [LinkedIn](https://www.linkedin.com/in/max-lee1) | [GitHub](https://github.com/maxolee23/) 122 | -------------------------------------------------------------------------------- /SpotifyStrategy/spotify_Auth.ts: -------------------------------------------------------------------------------- 1 | import { SpotifyClient } from './spotify_client.ts'; 2 | 3 | export abstract class SpotifyGrant { 4 | constructor( 5 | protected readonly client: SpotifyClient 6 | ) {} 7 | } 8 | 9 | export class SpotifyStrategy extends SpotifyGrant { 10 | constructor( 11 | client: SpotifyClient 12 | ) { 13 | super(client); 14 | } 15 | 16 | // SampleLink: string = `https://accounts.spotify.com/authorize?client_id={clientId}&scope=user-read-private&response_type=code&redirect_uri={redirect}&state=${state}` 17 | 18 | // part 1 of DenOAuth strategy 19 | /** Builds a URI you can redirect a user to to make the authorization request. */ 20 | createLink() { 21 | // The primary reason for using the state parameter is to mitigate CSRF attacks by using a unique and non-guessable value associated with each authentication request about to be initiated. 22 | const state: number = Math.floor(Math.random() * 1000000000) 23 | const SampleLink = `https://accounts.spotify.com/authorize?client_id=${this.client.config.clientId}&scope=${this.client.config.scope}&response_type=code&redirect_uri=${this.client.config.redirect}&state=${state}`; 24 | return SampleLink; 25 | } 26 | 27 | 28 | // part 2 of DenOAuth strategy 29 | async processAuth(stringPathName:any) { 30 | /** Parses the authorization response request tokens from the authorization server. */ 31 | const code:string = JSON.stringify(stringPathName.search); 32 | const parsedCode:string = code.slice(code.indexOf('"?code=')+7, code.indexOf('&state')); 33 | console.log(`parsedCode ${parsedCode}`) 34 | const userResponse:unknown[] = []; 35 | 36 | /** Exchange the authorization code for an access token */ 37 | await fetch('https://accounts.spotify.com/api/token',{ 38 | method: 'POST', 39 | headers: { 40 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" 41 | }, 42 | body: new URLSearchParams({ 43 | 'grant_type': "authorization_code", // hard code 44 | 'code': parsedCode, // helper function 45 | 'redirect_uri': this.client.config.redirect, // Spotify uri 46 | 'client_id': this.client.config.clientId, // provided by Spotify 47 | 'client_secret': this.client.config.clientSecret //provided by Spotify 48 | }) 49 | }) 50 | .then((response) => { 51 | console.log(`response ${response}`) 52 | return response.text() 53 | }) 54 | .then( async (paramsString: any) => { 55 | const params = new URLSearchParams(paramsString); 56 | console.log(`params ${params}`) 57 | const tokenKey = []; 58 | for (const [key, value] of params.entries()){ 59 | tokenKey.push(key, value) 60 | } 61 | 62 | const obj:any = tokenKey[0]; 63 | const values: unknown[] = Object.values(obj); 64 | const tokenArr: unknown[] = [] 65 | let i = 17; 66 | while (values[i] !== '"') { 67 | tokenArr.push(values[i]) 68 | i++ 69 | } 70 | const bearerToken = await tokenArr.join(''); 71 | console.log(`bearerToken ${bearerToken}`) 72 | /** Use the access token to make an authenticated API request */ 73 | await fetch("https://api.spotify.com/v1/me", { 74 | headers: { 75 | Authorization: `Bearer ${bearerToken}`, 76 | }, 77 | }) 78 | .then(response => response.json()) 79 | .then(data => { 80 | // Returning Spotify Response Data in console for Client 81 | console.log(`Spotify Response Data: ${data}`) 82 | userResponse.push(data) 83 | }) 84 | .catch(console.error) 85 | }) 86 | return userResponse[0]; 87 | } 88 | } -------------------------------------------------------------------------------- /SpotifyStrategy/spotify_Auth_test.ts: -------------------------------------------------------------------------------- 1 | import { SpotifyClient } from './spotify_client.ts'; 2 | // import { SpotifyStrategy } from './spotify_Auth.ts'; 3 | import { assertEquals } from "https://deno.land/std@0.119.0/testing/asserts.ts" 4 | 5 | Deno.test("Spotify's createLink method should return the correct url", () => { 6 | const client = new SpotifyClient({ 7 | clientId: '688zz8dnnxjo4t', 8 | clientSecret: 'YHhQQW3BaNQCFilB', 9 | redirect: 'http://localhost:3000/auth/spotify/callback', 10 | tokenUri: 'https://api.spotify.com/v1/me', 11 | scope: 'user-read-email' 12 | }); 13 | 14 | const dummy = client.code.createLink() 15 | const dummyState = dummy.slice((dummy.indexOf('&state') + 7)); 16 | 17 | assertEquals( 18 | dummy, 19 | `https://accounts.spotify.com/authorize?client_id=688zz8dnnxjo4t&scope=user-read-email&response_type=code&redirect_uri=http://localhost:3000/auth/spotify/callback&state=${dummyState}` 20 | ); 21 | }); 22 | 23 | Deno.test("parsedCode should parse the URL correctly", () => { 24 | 25 | const randomizedCode = Math.random().toString(36).substring(2,13); 26 | 27 | const fakeStringPathName = (`?code=${randomizedCode}&state=246276495`) 28 | const code:string = JSON.stringify(fakeStringPathName); 29 | const parsedCodeTest = code.slice(code.indexOf('"?code=')+7, code.indexOf('&state')); 30 | 31 | assertEquals( 32 | randomizedCode, 33 | parsedCodeTest 34 | ) 35 | }); -------------------------------------------------------------------------------- /SpotifyStrategy/spotify_client.ts: -------------------------------------------------------------------------------- 1 | import { SpotifyStrategy } from './spotify_Auth.ts'; 2 | 3 | export interface SpotifyClientConfig { 4 | /** The client ID provided by the authorization server. */ 5 | clientId:string; 6 | /** The client secret provided by the authorization server, if using a confidential client. Best practice to always keep secret in env file. */ 7 | clientSecret:string; 8 | /** The URI of the client's redirection endpoint (sometimes also called callback URI). */ 9 | redirect:string; 10 | /** The URI of the authorization server's token endpoint. */ 11 | tokenUri:string; 12 | 13 | // Our implementation currently only works with scope set to 'user-read-email' 14 | /** Scopes to request with the authorization request. */ 15 | scope: string | string[]; 16 | } 17 | 18 | 19 | export class SpotifyClient { 20 | // implements all the methods required to complete OAuth process 21 | public code = new SpotifyStrategy(this); 22 | 23 | // interface values cannot be changed outside of class 24 | constructor( 25 | public readonly config: Readonly, 26 | ) {} 27 | } -------------------------------------------------------------------------------- /SpotifyStrategy/spotify_client_test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'https://deno.land/std@0.119.0/testing/asserts.ts'; 2 | import { SpotifyClient } from './spotify_client.ts'; 3 | import { SpotifyStrategy } from './spotify_Auth.ts' 4 | 5 | Deno.test("SpotifyClient.code is created", () => { 6 | const client = new SpotifyClient({ 7 | clientId: "", 8 | clientSecret: "", 9 | redirect: "", 10 | tokenUri: "", 11 | scope: "" 12 | }); 13 | assert(client.code instanceof SpotifyStrategy); 14 | }); -------------------------------------------------------------------------------- /examples/Abc.ts: -------------------------------------------------------------------------------- 1 | import { Application } from "https://deno.land/x/abc@v1.3.3/mod.ts"; 2 | import { renderFile } from "https://deno.land/x/dejs@0.10.2/mod.ts"; 3 | import { GitHubClient, LinkedInClient, GoogleClient, SpotifyClient, DiscordClient } from 'https://deno.land/x/denoauth@v1.0.6/mod.ts' 4 | 5 | 6 | const app = new Application(); 7 | 8 | 9 | const GitHubObject = new GitHubClient({ 10 | clientId: '', 11 | clientSecret: '', 12 | tokenUri: 'https://github.com/login/oauth/access_token', 13 | redirect: "http://localhost:3000/auth/github/callback", 14 | scope: "read:user" 15 | }); 16 | 17 | const LinkedInObject = new LinkedInClient({ 18 | clientId: '', 19 | clientSecret: '', 20 | tokenUri: 'https://api.linkedin.com/v2/me', 21 | redirect: 'http://localhost:3000/auth/linkedin/callback', 22 | scope: 'r_liteprofile' 23 | }); 24 | 25 | const GoogleObject = new GoogleClient({ 26 | clientId: '', 27 | clientSecret: '', 28 | tokenUri: 'https://accounts.google.com/o/oauth2/token', 29 | redirect: 'http://localhost:3000/auth/google/callback', 30 | scope: 'https://mail.google.com&access_type=offline&include_granted_scopes=true' 31 | }); 32 | 33 | const SpotifyObject = new SpotifyClient({ 34 | clientId: '', 35 | clientSecret: '', 36 | tokenUri: 'https://api.spotify.com/v1/me', 37 | redirect: 'http://localhost:3000/auth/spotify/callback', 38 | scope: 'user-read-email' 39 | }); 40 | 41 | const DiscordObject = new DiscordClient({ 42 | clientId: '', 43 | clientSecret: '', 44 | tokenUri: 'https://discord.com/api/oauth2/token', 45 | redirect: 'http://localhost:3000/auth/discord/callback', 46 | scope: 'identify' 47 | }); 48 | 49 | 50 | app.static("/views", "./views/login.html"); 51 | 52 | 53 | app.renderer = { 54 | render(name: string, data: T): Promise { 55 | return renderFile(name, data); 56 | }, 57 | }; 58 | 59 | app.get('/login', async (c) => { 60 | await c.render('./login.html'); 61 | }) 62 | .start({ port: 3000 }); 63 | 64 | 65 | app.get('/gitHub', (c) => { 66 | c.redirect(GitHubObject.code.createLink()); 67 | }) 68 | 69 | app.get('/linkedin', (c) => { 70 | c.redirect(LinkedInObject.code.createLink()); 71 | }) 72 | 73 | app.get('/google', (c) => { 74 | c.redirect(GoogleObject.code.createLink()); 75 | }) 76 | 77 | app.get('/spotify', (c) => { 78 | c.redirect(SpotifyObject.code.createLink()); 79 | }) 80 | 81 | app.get('/discord', (c) => { 82 | c.redirect(DiscordObject.code.createLink()); 83 | }) 84 | 85 | app.get('/auth/github/callback', async (c) => { 86 | // Exchange the authorization code for an access token and exchange token for profile 87 | const userProfile: any = await GitHubObject.code.processAuth(c.url); 88 | // userProfile is an object of information given by GitHub. You can destructure the object to grab specific information 89 | const { name } = userProfile; 90 | 91 | return (`Hello, ${name}!`); 92 | }) 93 | 94 | app.get('/auth/linkedin/callback', async (c) => { 95 | // Exchange the authorization code for an access token and exchange token for profile 96 | const userProfile: any = await LinkedInObject.code.processAuth(c.url); 97 | // userProfile is an object of information given by LinkedIn. You can destructure the object to grab specific information 98 | const {localizedFirstName} = userProfile; 99 | 100 | return (`Hello ${localizedFirstName}`); 101 | }) 102 | 103 | 104 | app.get('/auth/google/callback', async (c) => { 105 | // Exchange the authorization code for an access token and exchange token for profile 106 | const userProfile: any = await GoogleObject.code.processAuth(c.url); 107 | // userProfile is an object of information given by Google. 108 | //You can destructure the object to grab specific information once the app has been verified 109 | return (`Hello, this is where your secret page lives`); 110 | }) 111 | 112 | app.get('/auth/spotify/callback', async (c) => { 113 | // Exchange the authorization code for an access token and exchange token for profile 114 | const userProfile: any = await SpotifyObject.code.processAuth(c.url); 115 | // userProfile is an object of information given by Spotify. You can destructure the object to grab specific information 116 | const { display_name } = userProfile; 117 | 118 | return (`Hello ${ display_name }`); 119 | }) 120 | 121 | app.get('/auth/discord/callback', async (c) => { 122 | // Exchange the authorization code for an access token and exchange token for profile 123 | const userProfile: any = await DiscordObject.code.processAuth(c.url); 124 | // userProfile is an object of information given by Discord. You can destructure the object to grab specific information 125 | const { username } = userProfile; 126 | 127 | console.log(`Hello ${ username }`); 128 | }) 129 | 130 | console.log("http://localhost:3000/"); 131 | -------------------------------------------------------------------------------- /examples/Oak.ts: -------------------------------------------------------------------------------- 1 | import { Application } from "https://deno.land/x/oak/mod.ts" 2 | import { renderFileToString } from "https://deno.land/x/dejs@0.10.2/mod.ts"; 3 | import { GitHubClient, LinkedInClient, GoogleClient, SpotifyClient, DiscordClient } from 'https://deno.land/x/denoauth@v1.0.6/mod.ts' 4 | import { Router } from "https://deno.land/x/oak/mod.ts" 5 | 6 | 7 | const GitHubObject = new GitHubClient({ 8 | clientId: '', 9 | clientSecret: "", 10 | tokenUri: 'https://github.com/login/oauth/access_token', 11 | redirect: "http://localhost:3000/auth/github/callback", 12 | scope: "read:user" 13 | }); 14 | 15 | const LinkedInObject = new LinkedInClient({ 16 | clientId: '', 17 | clientSecret: '', 18 | tokenUri: 'https://api.linkedin.com/v2/me', 19 | redirect: 'http://localhost:3000/auth/linkedin/callback', 20 | scope: 'r_liteprofile' 21 | }); 22 | 23 | const GoogleObject = new GoogleClient({ 24 | clientId: '', 25 | clientSecret: '', 26 | tokenUri: 'https://accounts.google.com/o/oauth2/token', 27 | redirect: 'http://localhost:3000/auth/google/callback', 28 | scope: 'https://mail.google.com&access_type=offline&include_granted_scopes=true' 29 | }); 30 | 31 | const SpotifyObject = new SpotifyClient({ 32 | clientId: '', 33 | clientSecret: '', 34 | tokenUri: 'https://api.spotify.com/v1/me', 35 | redirect: 'http://localhost:3000/auth/spotify/callback', 36 | scope: 'user-read-email' 37 | }); 38 | 39 | const DiscordObject = new DiscordClient({ 40 | clientId: '', 41 | clientSecret: '', 42 | tokenUri: 'https://discord.com/api/oauth2/token', 43 | redirect: 'http://localhost:3000/auth/discord/callback', 44 | scope: 'identify' 45 | }); 46 | 47 | const router = new Router(); 48 | 49 | const port: String|any = 3000 50 | const app = new Application() 51 | 52 | app.use(router.routes()); 53 | app.use(router.allowedMethods()); 54 | 55 | router.get('/login', async (ctx) => { 56 | ctx.response.body = await renderFileToString( 57 | `${Deno.cwd()}/views/login.html`, 58 | {}, 59 | ); 60 | }) 61 | 62 | router.get('/gitHub', (ctx) => { 63 | ctx.response.body = { 64 | message: 'success', 65 | data: ctx.response.redirect(GitHubObject.code.createLink()); 66 | }; 67 | }) 68 | router.get('/linkedin', (ctx) => { 69 | ctx.response.body = { 70 | message: 'success', 71 | data: ctx.response.redirect(LinkedInObject.code.createLink()); 72 | }; 73 | }) 74 | router.get('/google', (ctx) => { 75 | ctx.response.body = { 76 | message: 'success', 77 | data: ctx.response.redirect(GoogleObject.code.createLink()); 78 | }; 79 | }) 80 | router.get('/spotify', (ctx) => { 81 | ctx.response.body = { 82 | message: 'success', 83 | data: ctx.response.redirect(SpotifyObject.code.createLink()); 84 | }; 85 | }) 86 | router.get('/discord', (ctx) => { 87 | ctx.response.body = { 88 | message: 'success', 89 | data: ctx.response.redirect(DiscordObject.code.createLink()); 90 | }; 91 | }) 92 | 93 | router.get('/auth/github/callback', async (ctx) => { 94 | // Exchange the authorization code for an access token and exchange token for profile 95 | const userProfile: any = await GitHubObject.code.processAuth(ctx.request.url); 96 | // userProfile is an object of information given by GitHub. You can destructure the object to grab specific information 97 | const { name } = userProfile; 98 | 99 | ctx.response.body = `Hello, ${name}!`; 100 | }) 101 | 102 | router.get('/auth/linkedin/callback', async (ctx) => { 103 | // Exchange the authorization code for an access token and exchange token for profile 104 | const userProfile: any = await LinkedInObject.code.processAuth(ctx.request.url); 105 | // userProfile is an object of information given by LinkedIn. You can destructure the object to grab specific information 106 | const {localizedFirstName} = userProfile; 107 | 108 | ctx.response.body = `Hello ${localizedFirstName}`; 109 | }) 110 | 111 | 112 | router.get('/auth/google/callback', async (ctx) => { 113 | // Exchange the authorization code for an access token and exchange token for profile 114 | const userProfile: any = await GoogleObject.code.processAuth(ctx.request.url); 115 | // userProfile is an object of information given by Google. 116 | //You can destructure the object to grab specific information once the app has been verified 117 | ctx.response.body = `Hello, this is where your secret page lives`; 118 | }) 119 | 120 | router.get('/auth/spotify/callback', async (ctx) => { 121 | // Exchange the authorization code for an access token and exchange token for profile 122 | const userProfile: any = await SpotifyObject.code.processAuth(ctx.request.url); 123 | // userProfile is an object of information given by Spotify. You can destructure the object to grab specific information 124 | const { display_name } = userProfile; 125 | 126 | return (`Hello ${ display_name }`) 127 | }) 128 | 129 | router.get('/auth/discord/callback', async (ctx) => { 130 | // Exchange the authorization code for an access token and exchange token for profile 131 | const userProfile: any = await DiscordObject.code.processAuth(ctx.request.url); 132 | // userProfile is an object of information given by Discord. You can destructure the object to grab specific information 133 | const { username } = userProfile; 134 | 135 | console.log(`Hello ${ username }`); 136 | }) 137 | 138 | 139 | 140 | console.log(`Server running on port ${port}`); 141 | 142 | await app.listen({port: +port}); 143 | -------------------------------------------------------------------------------- /examples/Opine.ts: -------------------------------------------------------------------------------- 1 | import { opine, serveStatic } from "https://deno.land/x/opine@2.0.2/mod.ts"; 2 | import { renderFileToString } from "https://deno.land/x/dejs@0.10.2/mod.ts"; 3 | import { GitHubClient, LinkedInClient, GoogleClient, SpotifyClient, DiscordClient } from 'https://deno.land/x/denoauth@v1.0.6/mod.ts' 4 | 5 | 6 | const app = opine(); 7 | 8 | 9 | 10 | const GitHubObject = new GitHubClient({ 11 | clientId: '', 12 | clientSecret: "", 13 | tokenUri: 'https://github.com/login/oauth/access_token', 14 | redirect: "http://localhost:3000/auth/github/callback", 15 | scope: "read:user" 16 | }); 17 | 18 | const LinkedInObject = new LinkedInClient({ 19 | clientId: '', 20 | clientSecret: '', 21 | tokenUri: 'https://api.linkedin.com/v2/me', 22 | redirect: 'http://localhost:3000/auth/linkedin/callback', 23 | scope: 'r_liteprofile' 24 | }); 25 | 26 | const GoogleObject = new GoogleClient({ 27 | clientId: '', 28 | clientSecret: '', 29 | tokenUri: 'https://accounts.google.com/o/oauth2/token', 30 | redirect: 'http://localhost:3000/auth/google/callback', 31 | scope: 'https://mail.google.com&access_type=offline&include_granted_scopes=true' 32 | }); 33 | 34 | const SpotifyObject = new SpotifyClient({ 35 | clientId: '', 36 | clientSecret: '', 37 | tokenUri: 'https://api.spotify.com/v1/me', 38 | redirect: 'http://localhost:3000/auth/spotify/callback', 39 | scope: 'user-read-email' 40 | }); 41 | 42 | const DiscordObject = new DiscordClient({ 43 | clientId: '', 44 | clientSecret: '', 45 | tokenUri: 'https://discord.com/api/oauth2/token', 46 | redirect: 'http://localhost:3000/auth/discord/callback', 47 | scope: 'identify' 48 | }); 49 | 50 | app.engine('.html', renderFileToString); 51 | app.use(serveStatic("html")); 52 | 53 | app.get('/login', (req, res) => { 54 | res.render('login.html') 55 | }) 56 | 57 | app.get('/gitHub', (req, res) => { 58 | res.redirect(GitHubObject.code.createLink()) 59 | }) 60 | app.get('/linkedin', (req, res) => { 61 | res.redirect(LinkedInObject.code.createLink()) 62 | }) 63 | 64 | app.get('/google', (req, res) => { 65 | res.redirect(GoogleObject.code.createLink()) 66 | }) 67 | 68 | app.get('/spotify', (req, res) => { 69 | res.redirect(SpotifyObject.code.createLink()) 70 | }) 71 | 72 | app.get('/discord', (req, res) => { 73 | res.redirect(DiscordObject.code.createLink()) 74 | }) 75 | 76 | app.get('/auth/github/callback', async (req, res) => { 77 | // Exchange the authorization code for an access token and exchange token for profile 78 | const userProfile: any = await GitHubObject.code.processAuth(req._parsedUrl); 79 | // userProfile is an object of information given by GitHub. You can destructure the object to grab specific information 80 | const { name } = userProfile; 81 | 82 | res.send(`Hello, ${name}!`); 83 | }) 84 | 85 | app.get('/auth/linkedin/callback', async (req, res) => { 86 | // Exchange the authorization code for an access token and exchange token for profile 87 | const userProfile: any = await LinkedInObject.code.processAuth(req._parsedUrl); 88 | // userProfile is an object of information given by LinkedIn. You can destructure the object to grab specific information 89 | const {localizedFirstName} = userProfile; 90 | 91 | res.send(`Hello ${localizedFirstName}`); 92 | }) 93 | 94 | 95 | app.get('/auth/google/callback', async (req, res) => { 96 | // Exchange the authorization code for an access token and exchange token for profile 97 | const userProfile: any = await GoogleObject.code.processAuth(req._parsedUrl); 98 | // userProfile is an object of information given by Google. 99 | //You can destructure the object to grab specific information once the app has been verified 100 | res.send(`Hello, this is where your secret page lives`); 101 | }) 102 | 103 | app.get('/auth/spotify/callback', async (req, res) => { 104 | // Exchange the authorization code for an access token and exchange token for profile 105 | const userProfile: any = await SpotifyObject.code.processAuth(req.url); 106 | // userProfile is an object of information given by Spotify. You can destructure the object to grab specific information 107 | const { display_name } = userProfile; 108 | 109 | return (`Hello ${ display_name }`) 110 | }) 111 | 112 | app.get('/auth/discord/callback', async (req, res) => { 113 | // Exchange the authorization code for an access token and exchange token for profile 114 | const userProfile: any = await DiscordObject.code.processAuth(req.url); 115 | // userProfile is an object of information given by Discord. You can destructure the object to grab specific information 116 | const { username } = userProfile; 117 | 118 | console.log(`Hello ${ username }`); 119 | }) 120 | 121 | app.listen( 122 | 3000, 123 | () => console.log("server has started on http://localhost:3000 🚀"), 124 | ); 125 | -------------------------------------------------------------------------------- /examples/Pogo.ts: -------------------------------------------------------------------------------- 1 | import { GitHubClient, LinkedInClient, GoogleClient, SpotifyClient, DiscordClient } from 'https://deno.land/x/denoauth@v1.0.6/mod.ts' 2 | import pogo from 'https://deno.land/x/pogo/main.ts'; 3 | 4 | const server = pogo.server({ port : 3000 }); 5 | 6 | 7 | const GitHubObject = new GitHubClient({ 8 | clientId: '', 9 | clientSecret: "", 10 | tokenUri: 'https://github.com/login/oauth/access_token', 11 | redirect: "http://localhost:3000/auth/github/callback", 12 | scope: "read:user" 13 | }); 14 | 15 | const LinkedInObject = new LinkedInClient({ 16 | clientId: '', 17 | clientSecret: '', 18 | tokenUri: 'https://api.linkedin.com/v2/me', 19 | redirect: 'http://localhost:3000/auth/linkedin/callback', 20 | scope: 'r_liteprofile' 21 | }); 22 | 23 | const GoogleObject = new GoogleClient({ 24 | clientId: '', 25 | clientSecret: '', 26 | tokenUri: 'https://accounts.google.com/o/oauth2/token', 27 | redirect: 'http://localhost:3000/auth/google/callback', 28 | scope: 'https://mail.google.com&access_type=offline&include_granted_scopes=true' 29 | }); 30 | 31 | const SpotifyObject = new SpotifyClient({ 32 | clientId: '', 33 | clientSecret: '', 34 | tokenUri: 'https://api.spotify.com/v1/me', 35 | redirect: 'http://localhost:3000/auth/spotify/callback', 36 | scope: 'user-read-email' 37 | }); 38 | 39 | const DiscordObject = new DiscordClient({ 40 | clientId: '', 41 | clientSecret: '', 42 | tokenUri: 'https://discord.com/api/oauth2/token', 43 | redirect: 'http://localhost:3000/auth/discord/callback', 44 | scope: 'identify' 45 | }); 46 | 47 | 48 | server.router.get('/login', async (request, h) => { 49 | const buffer = await Deno.readFile('./login.html'); 50 | return h.response(buffer); 51 | }); 52 | 53 | server.router.get('/gitHub', (request, h) => { 54 | return h.redirect(GitHubObject.code.createLink()) 55 | }) 56 | 57 | server.router.get('/linkedin', (request, h) => { 58 | return h.redirect(LinkedInObject.code.createLink()) 59 | }) 60 | 61 | server.router.get('/google', (request, h) => { 62 | return h.redirect(GoogleObject.code.createLink()) 63 | }) 64 | 65 | server.router.get('/spotify', (request, h) => { 66 | return h.redirect(SpotifyObject.code.createLink()) 67 | }) 68 | 69 | server.router.get('/discord', (request, h) => { 70 | return h.redirect(DiscordObject.code.createLink()) 71 | }) 72 | 73 | server.router.get('/auth/github/callback', async (request, h) => { 74 | // Exchange the authorization code for an access token and exchange token for profile 75 | const userProfile: any = await GitHubObject.code.processAuth(request.url); 76 | // userProfile is an object of information given by GitHub. You can destructure the object to grab specific information 77 | const { name } = userProfile; 78 | 79 | return (`Hello, ${name}!`); 80 | }) 81 | 82 | server.router.get('/auth/linkedin/callback', async (request, h) => { 83 | // Exchange the authorization code for an access token and exchange token for profile 84 | const userProfile: any = await LinkedInObject.code.processAuth(request.url); 85 | // userProfile is an object of information given by LinkedIn. You can destructure the object to grab specific information 86 | const {localizedFirstName} = userProfile; 87 | 88 | return (`Hello ${localizedFirstName}`); 89 | }) 90 | 91 | 92 | server.router.get('/auth/google/callback', async (request, h) => { 93 | // Exchange the authorization code for an access token and exchange token for profile 94 | const userProfile: any = await GoogleObject.code.processAuth(request.url); 95 | // userProfile is an object of information given by Google. 96 | //You can destructure the object to grab specific information once the app has been verified 97 | return (`Hello, this is where your secret page lives`); 98 | }) 99 | 100 | server.router.get('/auth/spotify/callback', async (request, h) => { 101 | // Exchange the authorization code for an access token and exchange token for profile 102 | const userProfile: any = await SpotifyObject.code.processAuth(request.url); 103 | // userProfile is an object of information given by Spotify. You can destructure the object to grab specific information 104 | const { display_name } = userProfile; 105 | 106 | return (`Hello ${ display_name }`) 107 | }) 108 | 109 | server.router.get('/auth/discord/callback', async (request, h) => { 110 | // Exchange the authorization code for an access token and exchange token for profile 111 | const userProfile: any = await SpotifyObject.code.processAuth(request.url); 112 | // userProfile is an object of information given by Discord. You can destructure the object to grab specific information 113 | const { username } = userProfile; 114 | 115 | console.log(`Hello ${ username }`); 116 | }) 117 | 118 | server.router.get('/', () => { 119 | return 'Hello, world!'; 120 | }); 121 | 122 | server.start(); 123 | 124 | console.log("http://localhost:3000/"); 125 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export { LinkedInClient } from './LinkedInStrategy/linkedIn_client.ts'; 2 | export { GitHubClient } from './GitHubStrategy/GitHub_client.ts'; 3 | export { GoogleClient } from './GoogleStrategy/Google_client.ts'; 4 | export { SpotifyClient} from './SpotifyStrategy/spotify_client.ts'; 5 | export { DiscordClient} from './DiscordStrategy/Discord_client.ts'; --------------------------------------------------------------------------------