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 {generateCodeVerifier, generateCodeChallenge} from '@/helpers/pkce'
|
||||
import type {IProvider} from '@/types/IProvider'
|
||||
import {parseURL} from 'ufo'
|
||||
|
||||
|
|
@ -9,17 +10,21 @@ export function getRedirectUrlFromCurrentFrontendPath(provider: IProvider): stri
|
|||
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 state = createRandomID(24)
|
||||
localStorage.setItem('state', state)
|
||||
|
||||
const codeVerifier = generateCodeVerifier()
|
||||
const codeChallenge = await generateCodeChallenge(codeVerifier)
|
||||
sessionStorage.setItem('pkceCodeVerifier', codeVerifier)
|
||||
|
||||
let scope = 'openid email profile'
|
||||
if (provider.scope !== null){
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -243,9 +243,13 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
|
||||
const fullProvider: IProvider = configStore.auth.openidConnect.providers.find((p: IProvider) => p.key === provider)
|
||||
|
||||
const codeVerifier = sessionStorage.getItem('pkceCodeVerifier')
|
||||
sessionStorage.removeItem('pkceCodeVerifier')
|
||||
|
||||
const data = {
|
||||
code: code,
|
||||
redirect_url: getRedirectUrlFromCurrentFrontendPath(fullProvider),
|
||||
...(codeVerifier && {code_verifier: codeVerifier}),
|
||||
}
|
||||
|
||||
// Delete an eventually preexisting old token
|
||||
|
|
|
|||
|
|
@ -44,9 +44,10 @@ import (
|
|||
|
||||
// Callback contains the callback after an auth request was made and redirected
|
||||
type Callback struct {
|
||||
Code string `query:"code" json:"code"`
|
||||
Scope string `query:"scope" json:"scope"`
|
||||
RedirectURL string `json:"redirect_url"`
|
||||
Code string `query:"code" json:"code"`
|
||||
Scope string `query:"scope" json:"scope"`
|
||||
RedirectURL string `json:"redirect_url"`
|
||||
CodeVerifier string `json:"code_verifier"`
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
var rerr *oauth2.RetrieveError
|
||||
if errors.As(err, &rerr) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue