Compare commits
4 Commits
main
...
fix-oidc-p
| Author | SHA1 | Date |
|---|---|---|
|
|
f24b15c6e9 | |
|
|
4c565537e4 | |
|
|
f5024e2f2c | |
|
|
fb8e4ea741 |
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Generate a cryptographically random code_verifier (43-128 chars, RFC 7636 Section 4.1).
|
||||||
|
* Uses unreserved characters: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
||||||
|
*/
|
||||||
|
export function generateCodeVerifier(): string {
|
||||||
|
const array = new Uint8Array(32)
|
||||||
|
crypto.getRandomValues(array)
|
||||||
|
return base64UrlEncode(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute code_challenge = BASE64URL(SHA256(code_verifier)) (RFC 7636 Section 4.2).
|
||||||
|
*/
|
||||||
|
export async function generateCodeChallenge(verifier: string): Promise<string> {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const data = encoder.encode(verifier)
|
||||||
|
const digest = await crypto.subtle.digest('SHA-256', data)
|
||||||
|
return base64UrlEncode(new Uint8Array(digest))
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64UrlEncode(bytes: Uint8Array): string {
|
||||||
|
let binary = ''
|
||||||
|
for (const byte of bytes) {
|
||||||
|
binary += String.fromCharCode(byte)
|
||||||
|
}
|
||||||
|
return btoa(binary)
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=+$/, '')
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {createRandomID} from '@/helpers/randomId'
|
import {createRandomID} from '@/helpers/randomId'
|
||||||
|
import {generateCodeVerifier, generateCodeChallenge} from '@/helpers/pkce'
|
||||||
import type {IProvider} from '@/types/IProvider'
|
import type {IProvider} from '@/types/IProvider'
|
||||||
import {parseURL} from 'ufo'
|
import {parseURL} from 'ufo'
|
||||||
|
|
||||||
|
|
@ -9,17 +10,21 @@ export function getRedirectUrlFromCurrentFrontendPath(provider: IProvider): stri
|
||||||
return `${url.protocol}//${url.host}/auth/openid/${provider.key}`
|
return `${url.protocol}//${url.host}/auth/openid/${provider.key}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const redirectToProvider = (provider: IProvider) => {
|
export const redirectToProvider = async (provider: IProvider) => {
|
||||||
|
|
||||||
const redirectUrl = getRedirectUrlFromCurrentFrontendPath(provider)
|
const redirectUrl = getRedirectUrlFromCurrentFrontendPath(provider)
|
||||||
const state = createRandomID(24)
|
const state = createRandomID(24)
|
||||||
localStorage.setItem('state', state)
|
localStorage.setItem('state', state)
|
||||||
|
|
||||||
|
const codeVerifier = generateCodeVerifier()
|
||||||
|
const codeChallenge = await generateCodeChallenge(codeVerifier)
|
||||||
|
sessionStorage.setItem('pkceCodeVerifier', codeVerifier)
|
||||||
|
|
||||||
let scope = 'openid email profile'
|
let scope = 'openid email profile'
|
||||||
if (provider.scope !== null){
|
if (provider.scope !== null){
|
||||||
scope = provider.scope
|
scope = provider.scope
|
||||||
}
|
}
|
||||||
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${state}`
|
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const redirectToProviderOnLogout = (provider: IProvider) => {
|
export const redirectToProviderOnLogout = (provider: IProvider) => {
|
||||||
|
|
|
||||||
|
|
@ -243,9 +243,13 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
|
|
||||||
const fullProvider: IProvider = configStore.auth.openidConnect.providers.find((p: IProvider) => p.key === provider)
|
const fullProvider: IProvider = configStore.auth.openidConnect.providers.find((p: IProvider) => p.key === provider)
|
||||||
|
|
||||||
|
const codeVerifier = sessionStorage.getItem('pkceCodeVerifier')
|
||||||
|
sessionStorage.removeItem('pkceCodeVerifier')
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
code: code,
|
code: code,
|
||||||
redirect_url: getRedirectUrlFromCurrentFrontendPath(fullProvider),
|
redirect_url: getRedirectUrlFromCurrentFrontendPath(fullProvider),
|
||||||
|
...(codeVerifier && {code_verifier: codeVerifier}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete an eventually preexisting old token
|
// Delete an eventually preexisting old token
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,10 @@ import (
|
||||||
|
|
||||||
// Callback contains the callback after an auth request was made and redirected
|
// Callback contains the callback after an auth request was made and redirected
|
||||||
type Callback struct {
|
type Callback struct {
|
||||||
Code string `query:"code" json:"code"`
|
Code string `query:"code" json:"code"`
|
||||||
Scope string `query:"scope" json:"scope"`
|
Scope string `query:"scope" json:"scope"`
|
||||||
RedirectURL string `json:"redirect_url"`
|
RedirectURL string `json:"redirect_url"`
|
||||||
|
CodeVerifier string `json:"code_verifier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider is the structure of an OpenID Connect provider
|
// Provider is the structure of an OpenID Connect provider
|
||||||
|
|
@ -468,7 +469,11 @@ func getProviderAndOidcTokens(c *echo.Context) (*Provider, *oauth2.Token, *oidc.
|
||||||
|
|
||||||
provider.Oauth2Config.RedirectURL = cb.RedirectURL
|
provider.Oauth2Config.RedirectURL = cb.RedirectURL
|
||||||
// Parse the access & ID token
|
// Parse the access & ID token
|
||||||
oauth2Token, err := provider.Oauth2Config.Exchange(context.Background(), cb.Code)
|
var exchangeOpts []oauth2.AuthCodeOption
|
||||||
|
if cb.CodeVerifier != "" {
|
||||||
|
exchangeOpts = append(exchangeOpts, oauth2.SetAuthURLParam("code_verifier", cb.CodeVerifier))
|
||||||
|
}
|
||||||
|
oauth2Token, err := provider.Oauth2Config.Exchange(context.Background(), cb.Code, exchangeOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var rerr *oauth2.RetrieveError
|
var rerr *oauth2.RetrieveError
|
||||||
if errors.As(err, &rerr) {
|
if errors.As(err, &rerr) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue