Skip to content
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

Refresh tokens #7

Merged
merged 11 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ And after that you redirect to the callback route `<CALLBACK_URL>` with query st
try {
const authorizationCode = "12345"

const response = await auth.getTokens(authorizationCode) // return Promise
const response = await auth.fetchTokens(authorizationCode) // return Promise
const tokens = response.json()

const { accessToken, refreshToken } = tokens
Expand Down Expand Up @@ -89,6 +89,32 @@ try {
}
```

#### Authentication

Authentication method for verifying access and refresh tokens and scheduling tokens refreshing.

```js
const getTokens = () => {
const accessToken = localStorage.getItem("accessToken")
const refreshToken = localStorage.getItem("refreshToken")

return { accessToken, refreshToken }
}

const saveTokens = ({ accessToken, refreshToken }) => {
localStorage.setItem("accessToken", accessToken)
localStorage.setItem("refreshToken", refreshToken)
}

//async/await
try {
await auth.authentication({ getTokens, saveTokens })
...
} catch (error) {
throw Error(error.message) // token.not_found
}
```

### Additional usage

#### Decode token
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cadolabs/auther-client",
"version": "0.2.0",
"version": "1.0.0",
"main": "dist/auther-client.cjs.js",
"module": "dist/auther-client.es.js",
"description": "Client for working with auther on the frontend side",
Expand Down
72 changes: 70 additions & 2 deletions src/client/AutherClient.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { verify, decode } from "../lib/jwt"

const ONE_MINUTE_MS = 60 * 1000
const ONE_DAY_MS = 24 * 60 * 60 * 1000

export class AutherClient {
#location = window.location

Expand All @@ -6,11 +11,12 @@ export class AutherClient {
#REFRESH_PATH = "/tokens/refresh"
#LOGIN_PATH = "/login"

constructor ({ redirectUri, autherUrl, http, appcode }) {
constructor ({ redirectUri, autherUrl, http, appcode, logger }) {
this.redirectUri = redirectUri
this.autherUrl = autherUrl
this.http = http
this.appcode = appcode
this.logger = logger
}

#buildOauthUrl = () => {
Expand All @@ -23,6 +29,56 @@ export class AutherClient {
return redirectUrl.toString()
}

#refreshTokens = async ({ getTokens, saveTokens }) => {
const currentTime = `${new Date()} [${new Date().toUTCString()}]`
const { refreshToken } = getTokens()

verify(refreshToken)

const response = await this.updateTokens(refreshToken)
const tokens = await response.json()

saveTokens(tokens)

this.logger.log(`Token has been refreshed successfully at ${currentTime}`)
}

#scheduleTokensRefreshing = ({ getTokens, saveTokens }) => {
const { accessToken } = getTokens()

verify(accessToken)

const decodedToken = decode(accessToken)
const tokenExpDateMs = decodedToken.payload.exp * 1000
let refreshTimeout = (tokenExpDateMs - new Date()) / 2

if (refreshTimeout < ONE_MINUTE_MS) {
refreshTimeout = ONE_MINUTE_MS
}

if (refreshTimeout > ONE_DAY_MS) {
elenik72 marked this conversation as resolved.
Show resolved Hide resolved
refreshTimeout = ONE_DAY_MS
}

const tokenExpDate = new Date(tokenExpDateMs)
const refreshDate = new Date(Date.now() + refreshTimeout)

this.logger.log(`Token will expire at ${(tokenExpDate)} [${tokenExpDate.toUTCString()}]`)
this.logger.log(`Token will be refreshed at ${refreshDate} [${refreshDate.toUTCString()}]`)

setTimeout(async () => {
try {
await this.#refreshTokens({ getTokens, saveTokens })
this.#scheduleTokensRefreshing({ getTokens, saveTokens })
}
catch (error) {
this.logger.error(
`Error during tokens refreshing at ${new Date()} [${new Date().toUTCString()}]`,
)
}
}, refreshTimeout)
}

login = () => {
return this.#location.replace(this.#buildOauthUrl())
}
Expand All @@ -38,7 +94,7 @@ export class AutherClient {
})
}

getTokens = authorizationCode => {
fetchTokens = authorizationCode => {
if (!authorizationCode) {
throw new Error("invalid.authorization_code")
}
Expand All @@ -56,4 +112,16 @@ export class AutherClient {

return this.http({ path: this.#REFRESH_PATH, body: { refreshToken } })
}

authentication = async ({ getTokens, saveTokens }) => {
const { accessToken } = getTokens()
try {
verify(accessToken)
}
catch (err) {
await this.#refreshTokens({ getTokens, saveTokens })
}

this.#scheduleTokensRefreshing({ getTokens, saveTokens })
}
}
3 changes: 2 additions & 1 deletion src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { doFetch } from "../lib/http"
import { AutherClient } from "./AutherClient"

export default class {
static init ({ redirectUri, autherUrl, appcode }) {
static init ({ redirectUri, autherUrl, appcode, logger = console }) {
return new AutherClient({
http: doFetch(autherUrl),
redirectUri,
autherUrl,
appcode,
logger,
})
}
}
3 changes: 2 additions & 1 deletion src/lib/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ const verify = token => {
}

const { payload } = decode(token)
const currentTime = new Date().getTime()

if (!payload.iat || !payload.exp) {
throw new Error("token.invalid")
}

const currentTime = new Date().getTime()

if (currentTime > payload.exp * 1000) {
throw new Error("token.expired")
}
Expand Down
Loading
Loading