Skip to main content
Gå til innhold

Vite.js

Initiate autentication:

import * as oauth from "oauth4webapi"

const code_challenge_method = "S256"

export class OAuthClient {
private issuer!: URL
private client!: oauth.Client
private clientAuth!: oauth.ClientAuth
private redirect_uri!: string
private state: string | undefined

constructor() {

const client_id: string = import.meta.env.VITE_FEIDE_CLIENT_ID
const client_secret: string = import.meta.env.VITE_FEIDE_CLIENT_SECRET

this.issuer = new URL("https://auth.dataporten.no/")
this.client = { client_id }
this.clientAuth = oauth.ClientSecretPost(client_secret)
this.redirect_uri = `${window.location.origin}/oauth2-redirect-feide`
}

// To be used to authenticate by redirecting to authorization url
async authenticate() {
const as = await oauth
.discoveryRequest(this.issuer)
.then((response) => oauth.processDiscoveryResponse(this.issuer, response))

const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)

const redirectPath =
window.location.pathname !== "/logg-ut" ? `${window.location.pathname}${window.location.search}` : "/"
sessionStorage.setItem("redirectAfterLogin", JSON.stringify(redirectPath))
sessionStorage.setItem("cv", code_verifier)

const authorizationUrl = new URL(as.authorization_endpoint!)
authorizationUrl.searchParams.set("client_id", this.client.client_id)
authorizationUrl.searchParams.set("redirect_uri", this.redirect_uri)
authorizationUrl.searchParams.set("response_type", "code")
authorizationUrl.searchParams.set("scope", "openid")
authorizationUrl.searchParams.set("code_challenge", code_challenge)
authorizationUrl.searchParams.set("code_challenge_method", code_challenge_method)

if (as.code_challenge_methods_supported?.includes("S256") !== true) {
this.state = oauth.generateRandomState()
authorizationUrl.searchParams.set("state", this.state)
sessionStorage.setItem("state", this.state)
}

window.location.href = authorizationUrl.href
}

// To be used from the redirect_uri to perform Authorization Code Grant Request
async exchangeAuthorizationCode(state: string | undefined) {
const currentUrl = new URL(window.location.href)
const as = await oauth
.discoveryRequest(this.issuer)
.then((response) => oauth.processDiscoveryResponse(this.issuer, response))

const code_verifier = sessionStorage.getItem("cv")
if (!code_verifier) {
throw new Error("Code verifier not found in session storage.")
}

const params = oauth.validateAuthResponse(as, this.client, currentUrl, state)

const response = await oauth.authorizationCodeGrantRequest(
as,
this.client,
this.clientAuth,
params,
this.redirect_uri,
code_verifier
)

const result = await oauth.processAuthorizationCodeResponse(as, this.client, response)

return result
}

// Only for Feide
async tokenExchange(
access_token: string,
audience: string = "d2404523-b3f4-4926-b24d-4421f2b5fd51"
): Promise<oauth.TokenEndpointResponse> {
if (this.provider !== "Feide") {
throw new Error("tokenExchange method is only available for Feide provider")
}

const tokenEndpoint = "https://auth.dataporten.no/oauth/token"
const client_secret = import.meta.env.VITE_FEIDE_CLIENT_SECRET
const body = new URLSearchParams()

body.append("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
body.append("client_id", this.client.client_id)
body.append("client_secret", client_secret)
body.append("audience", "https://n.feide.no/datasources/" + audience)
body.append("subject_token_type", "urn:ietf:params:oauth:token-type:access_token")
body.append("subject_token", access_token)
body.append("scope", "usercredential")

return await fetch(tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: body,
}).then((response) => response.json())
}

// Only for Azure
async refreshTokenExchange(refresh_token: string) {
const as = await oauth
.discoveryRequest(this.issuer)
.then((response) => oauth.processDiscoveryResponse(this.issuer, response))

const response = await oauth.refreshTokenGrantRequest(as, this.client, this.clientAuth, refresh_token)

return oauth.processRefreshTokenResponse(as, this.client, response)
}

async getUserInfo(result: oauth.TokenEndpointResponse) {
const claims = oauth.getValidatedIdTokenClaims(result)!
const { sub } = claims

const { access_token } = result

const as = await oauth
.discoveryRequest(this.issuer)
.then((response) => oauth.processDiscoveryResponse(this.issuer, response))

// Send request for user info
const response = await oauth.userInfoRequest(as, this.client, access_token)

const userResult = await oauth.processUserInfoResponse(as, this.client, sub, response)

return userResult
}

logout() {
const prefix = this.provider.toLowerCase()

// Iterate over sessionStorage keys and remove items that match the prefix
for (let i = sessionStorage.length - 1; i >= 0; i--) {
const key = sessionStorage.key(i)
if (key && key.startsWith(prefix)) {
sessionStorage.removeItem(key)
}
}

if (this.provider === "Feide") {
window.location.assign("/logg-ut")
} else {
window.location.reload()
}
}
}

Callback endpoint:

// Get an access token
const state = sessionStorage.getItem("state"); // null for Feide, string for Azure
const result = await authClient.exchangeAuthorizationCode(
state ? state : undefined
);
const { access_token, refresh_token } = result;
sessionStorage.setItem(`${provider.toLowerCase()}_access_token`, access_token); // valid for 8 hours for Feide, 60-90 min for Azure
if (refresh_token) {
sessionStorage.setItem("azure_refresh_token", refresh_token); // Azure only. Valid for 24 hours
}

if (provider === "Feide") {
// Exchange the access token for a JWT token
const data = await authClient.tokenExchange(access_token);
const bearerToken = data.access_token;
sessionStorage.setItem("feide_bearer_token", bearerToken); // valid for 5 minutes
}

// Get authenticated user info
const userInfo = await authClient.getUserInfo(result);
if (provider === "Feide") {
sessionStorage.setItem("feide_user_info", JSON.stringify(userInfo));
}

sessionStorage.removeItem("cv");
sessionStorage.removeItem("state");

const redirectAfterLogin = sessionStorage.getItem("redirectAfterLogin");
navigate(redirectAfterLogin ? JSON.parse(redirectAfterLogin) : "/");