-
Notifications
You must be signed in to change notification settings - Fork 25
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
feat: createAzureAdOAuthConfig()
and createAzureAdb2cOAuthConfig()
#293
Conversation
Still working on some issues with azure ADB2C support involving some query string params that are needed for that, and proper redirect |
Support for Azure and ADB2C are working |
To use this until it gets merged, you can use an import map like this:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay and thanks for the PR! It'll be good to have this functionality added. A few suggestions, mostly around sticking to how other createXOAuthConfig()
functions are implemented.
lib/create_azure_oauth_config.ts
Outdated
*/ | ||
export function createAzureOAuthConfig(config: { | ||
/** Used to set the prefix used to access environment variables; defaults to `AZURE`.*/ | ||
envPrefix?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This option doesn't exist for any of the other platform config creator functions. Can you please remove it? A single, hardcoded environment variable prefix was a deliberate design decision to simplify logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anyway we can include this change? A few reasons, 1) I was going to create a pr to add it to the other ones. When doing the ahead of time builds with the github oauth provider, this fails as github actions do not allow environment variables that start with GITHUB_
. 2) by design, and due to azure capabilities, we have to use two separate Azure providers. One for oauth provider, and the other for access to azure infrastructure via oath access tokens. 3) The default env tokens we would use clash with existing environment variables that are used, and established, in this and other projects. I imagine that other users will eventually run into this clash when adding this library in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also tried to keep it inline with other impls by defaulting to the singular AZURE
suffix to keep inline when envPrefix is omitted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's hardcode the environment variable prefix for now, like for every other provider. We've kept this API's library simple and consistent while working for most use cases. Edge cases can be handled by using custom OAuth configs.
lib/create_azure_oauth_config.ts
Outdated
* | ||
* @see {@link https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc} | ||
*/ | ||
export function createAzureOAuthConfig(config: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function differs in its logic from other similar functions within this library. For ease of management of use, could you please try following the implementation of other createXOAuthConfig()
functions in the library?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two major differences i see are 1) related to the logic around envPrefix, hoping we can include that as i'm not sure how i would support our need for multiple Azure oAuth providers without, and 2) around the ClientID logic. For the client ID logic, with ClientID being required in scopes for the oAuth flow to work, i figured it would be best to add in the createXOauthConfig so that users did not have to try and access additional environment variables during setup of the OAuth config so that user configuration was more closely related to other createXOAuthConfig methods. Would the preference be that we as maintainers deal with this nuance in code? Or that we push it on the user to need to access the environment variable during setup, diverging from how the other ones work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PTAL at my suggested implementation. Can you please also add a test to lib/create_oauth_config_test.ts
? You may need to add the following to the test:
// For Azure
Deno.env.set(`${envPrefix}_TENANT_ID`, crypto.randomUUID());
Regarding multiple providers using for the same platform, let's open a separate issue to discuss. I feel like such a use might just warrant a custom OAuth configuration.
lib/create_azure_oauth_config.ts
Outdated
/** | ||
* Returns the OAuth configuration for Azure. | ||
* | ||
* Requires `--allow-env[=AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_CLOUD_INSTANCE,AZURE_TENANT_ID]` | ||
* permissions and environment variables: | ||
* 1. `AZURE_CLIENT_ID` | ||
* 2. `AZURE_CLIENT_SECRET` | ||
* 3. `AZURE_CLOUD_INSTANCE` | ||
* 4. `AZURE_TENANT_ID` | ||
* | ||
* @example | ||
* ```ts | ||
* import { createAzureOAuthConfig } from "https://deno.land/x/deno_kv_oauth@$VERSION/mod.ts"; | ||
* | ||
* const oauthConfig = createAzureOAuthConfig({ | ||
* envPrefix: "AZURE", | ||
* redirectUri: "http://localhost:8000/callback", | ||
* scope: "openid", | ||
* }); | ||
* ``` | ||
* | ||
* You can use a separate environment variable prefix with the envPrefix configureation. | ||
* Requires `--allow-env[=MY_PREFIX_CLIENT_ID,MY_PREFIX_CLIENT_SECRET,MY_PREFIX_CLOUD_INSTANCE,MY_PREFIX_TENANT_ID]` | ||
* permissions and environment variables: | ||
* 1. `MY_PREFIX_CLIENT_ID` | ||
* 2. `MY_PREFIX_CLIENT_SECRET` | ||
* 3. `MY_PREFIX_CLOUD_INSTANCE` | ||
* 4. `MY_PREFIX_TENANT_ID` | ||
* | ||
* @example | ||
* ```ts | ||
* import { createAzureOAuthConfig } from "https://deno.land/x/deno_kv_oauth@$VERSION/mod.ts"; | ||
* | ||
* const oauthConfig = createAzureOAuthConfig({ | ||
* envPrefix: "MY_PREFIX", | ||
* redirectUri: "http://localhost:8000/callback", | ||
* scope: "openid", | ||
* }); | ||
* ``` | ||
* | ||
* This also supports the ability to connect with Azure AD B2C instances using the | ||
* proper configurations for AD B2C instances | ||
* | ||
* @see {@link https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc} | ||
*/ | ||
export function createAzureOAuthConfig(config: { | ||
/** Used to set the prefix used to access environment variables; defaults to `AZURE`.*/ | ||
envPrefix?: string; | ||
/** @see {@linkcode OAuth2ClientConfig.redirectUri} */ | ||
redirectUri?: string; | ||
/** @see {@linkcode OAuth2ClientConfig.defaults.scope} */ | ||
scope: string | string[]; | ||
}): OAuth2ClientConfig { | ||
const getEnvKey = (key: string) => { | ||
return `${config.envPrefix || "AZURE"}_${key}`; | ||
}; | ||
|
||
let cloudInstance = getRequiredEnv(getEnvKey("CLOUD_INSTANCE")); | ||
|
||
if (!cloudInstance.endsWith("/")) { | ||
cloudInstance = `${cloudInstance}/`; | ||
} | ||
|
||
const tenantId = getRequiredEnv(getEnvKey("TENANT_ID")); | ||
|
||
const policy = Deno.env.get(getEnvKey("POLICY")); | ||
|
||
const path = policy ? `${tenantId}/${policy}` : tenantId; | ||
|
||
const baseURL = `${cloudInstance}${path}/oauth2`; | ||
|
||
const clientId = getRequiredEnv(getEnvKey("CLIENT_ID")); | ||
|
||
if (Array.isArray(config.scope) && config.scope.some((s) => s === "openid")) { | ||
config.scope.push(clientId); | ||
} else if ( | ||
typeof config.scope === "string" && | ||
config.scope.includes("openid") | ||
) { | ||
config.scope = `${config.scope} ${clientId}`; | ||
} | ||
|
||
return { | ||
clientId, | ||
clientSecret: getRequiredEnv(getEnvKey("CLIENT_SECRET")), | ||
authorizationEndpointUri: `${baseURL}/v2.0/authorize`, | ||
tokenUri: `${baseURL}/v2.0/token`, | ||
redirectUri: config.redirectUri, | ||
defaults: { | ||
scope: config.scope, | ||
}, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** | |
* Returns the OAuth configuration for Azure. | |
* | |
* Requires `--allow-env[=AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_CLOUD_INSTANCE,AZURE_TENANT_ID]` | |
* permissions and environment variables: | |
* 1. `AZURE_CLIENT_ID` | |
* 2. `AZURE_CLIENT_SECRET` | |
* 3. `AZURE_CLOUD_INSTANCE` | |
* 4. `AZURE_TENANT_ID` | |
* | |
* @example | |
* ```ts | |
* import { createAzureOAuthConfig } from "https://deno.land/x/deno_kv_oauth@$VERSION/mod.ts"; | |
* | |
* const oauthConfig = createAzureOAuthConfig({ | |
* envPrefix: "AZURE", | |
* redirectUri: "http://localhost:8000/callback", | |
* scope: "openid", | |
* }); | |
* ``` | |
* | |
* You can use a separate environment variable prefix with the envPrefix configureation. | |
* Requires `--allow-env[=MY_PREFIX_CLIENT_ID,MY_PREFIX_CLIENT_SECRET,MY_PREFIX_CLOUD_INSTANCE,MY_PREFIX_TENANT_ID]` | |
* permissions and environment variables: | |
* 1. `MY_PREFIX_CLIENT_ID` | |
* 2. `MY_PREFIX_CLIENT_SECRET` | |
* 3. `MY_PREFIX_CLOUD_INSTANCE` | |
* 4. `MY_PREFIX_TENANT_ID` | |
* | |
* @example | |
* ```ts | |
* import { createAzureOAuthConfig } from "https://deno.land/x/deno_kv_oauth@$VERSION/mod.ts"; | |
* | |
* const oauthConfig = createAzureOAuthConfig({ | |
* envPrefix: "MY_PREFIX", | |
* redirectUri: "http://localhost:8000/callback", | |
* scope: "openid", | |
* }); | |
* ``` | |
* | |
* This also supports the ability to connect with Azure AD B2C instances using the | |
* proper configurations for AD B2C instances | |
* | |
* @see {@link https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc} | |
*/ | |
export function createAzureOAuthConfig(config: { | |
/** Used to set the prefix used to access environment variables; defaults to `AZURE`.*/ | |
envPrefix?: string; | |
/** @see {@linkcode OAuth2ClientConfig.redirectUri} */ | |
redirectUri?: string; | |
/** @see {@linkcode OAuth2ClientConfig.defaults.scope} */ | |
scope: string | string[]; | |
}): OAuth2ClientConfig { | |
const getEnvKey = (key: string) => { | |
return `${config.envPrefix || "AZURE"}_${key}`; | |
}; | |
let cloudInstance = getRequiredEnv(getEnvKey("CLOUD_INSTANCE")); | |
if (!cloudInstance.endsWith("/")) { | |
cloudInstance = `${cloudInstance}/`; | |
} | |
const tenantId = getRequiredEnv(getEnvKey("TENANT_ID")); | |
const policy = Deno.env.get(getEnvKey("POLICY")); | |
const path = policy ? `${tenantId}/${policy}` : tenantId; | |
const baseURL = `${cloudInstance}${path}/oauth2`; | |
const clientId = getRequiredEnv(getEnvKey("CLIENT_ID")); | |
if (Array.isArray(config.scope) && config.scope.some((s) => s === "openid")) { | |
config.scope.push(clientId); | |
} else if ( | |
typeof config.scope === "string" && | |
config.scope.includes("openid") | |
) { | |
config.scope = `${config.scope} ${clientId}`; | |
} | |
return { | |
clientId, | |
clientSecret: getRequiredEnv(getEnvKey("CLIENT_SECRET")), | |
authorizationEndpointUri: `${baseURL}/v2.0/authorize`, | |
tokenUri: `${baseURL}/v2.0/token`, | |
redirectUri: config.redirectUri, | |
defaults: { | |
scope: config.scope, | |
}, | |
}; | |
} | |
/** | |
* Returns the OAuth configuration for Azure. | |
* | |
* Requires `--allow-env[=AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_TENANT_ID]` | |
* permissions and environment variables: | |
* 1. `AZURE_CLIENT_ID` | |
* 2. `AZURE_CLIENT_SECRET` | |
* 3. `AZURE_TENANT_ID` | |
* | |
* @example | |
* ```ts | |
* import { createAzureOAuthConfig } from "https://deno.land/x/deno_kv_oauth@$VERSION/mod.ts"; | |
* | |
* const oauthConfig = createAzureOAuthConfig({ | |
* redirectUri: "http://localhost:8000/callback", | |
* scope: "https://graph.microsoft.com/User.Read", | |
* }); | |
* ``` | |
* | |
* @see {@link https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow} | |
*/ | |
export function createAzureOAuthConfig(config: { | |
/** @see {@linkcode OAuth2ClientConfig.redirectUri} */ | |
redirectUri?: string; | |
/** @see {@linkcode OAuth2ClientConfig.defaults.scope} */ | |
scope: string | string[]; | |
}): OAuth2ClientConfig { | |
const baseUrl = `https://login.microsoftonline.com/${ | |
getRequiredEnv("AZURE_TENANT_ID") | |
}/oauth2/v2.0`; | |
return { | |
clientId: getRequiredEnv("AZURE_CLIENT_ID"), | |
clientSecret: getRequiredEnv("AZURE_CLIENT_SECRET"), | |
authorizationEndpointUri: `${baseUrl}/authorize`, | |
tokenUri: `${baseUrl}/token`, | |
redirectUri: config.redirectUri, | |
defaults: { | |
scope: config.scope, | |
}, | |
}; | |
} |
These changes bring the Azure implementation into line with the other platforms. I've tested it, and it works as expected. I want to clarify that these functions are meant to cater to the majority of use cases, not all use cases. Advanced use cases can take advantage of custom OAuth configs, if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With these changes azure ADB2C won't work, which is actually what we need to use, the major issue being the hardcoding of https://login.microsoftonline.com. I split AD and ADB2C into separate config providers. Though i still think we should include the logic around client ID, otherwise the end user experience is more complicated by having to know to add the client ID as a scope instead of just openid
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if you want me to remove the client ID logic.
Tests were added as well.
With the separation of Azure ADB2C from Azure AD, this would work for us now. |
Apologies for the delay, @mcgear. I don't like to drag PRs on for this long. I'll review this PR by the end of this week. |
Sounds good, i'll try to be quick on incorporating any other changes, but i think i got both of the new ones inline with your other comments. The only real last question is whether or not we keep the ClientID code in or force end users to add it themselves when leveraging openid |
The user should add the client ID if they want to leverage OpenID. Looking at the current implementations, they still include logic that isn't present for other platforms. Again, can you please remove this logic? |
Those changes are in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking much better! Can you please provide ensure you've followed these steps in this PR so we know the client functions are working?
README.md
Outdated
@@ -314,6 +314,7 @@ is set in the following order of precedence: | |||
The following providers have pre-defined OAuth configurations: | |||
|
|||
1. [Auth0](https://deno.land/x/deno_kv_oauth/mod.ts?s=createAuth0OAuthConfig) | |||
1. [Azure](https://deno.land/x/deno_kv_oauth/mod.ts?s=createAzureOAuthConfig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please update this with the two new functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed the readme
2 and 3 from that link are taken care of, i'm not sure what to do for 1 as the link on contributing is broken. I am using both configurations in a deployment, and they are working. Let me know what sort of screenshot i should be providing. |
* | ||
* @see {@link https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc} | ||
*/ | ||
export function createAzureADB2COAuthConfig(config: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function createAzureADB2COAuthConfig(config: { | |
export function createAzureAdb2cOAuthConfig(config: { |
Ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
lib/create_azure_ad_oauth_config.ts
Outdated
* | ||
* @see {@link https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc} | ||
*/ | ||
export function createAzureADOAuthConfig(config: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function createAzureADOAuthConfig(config: { | |
export function createAzureAdOAuthConfig(config: { |
Nit: the Deno naming convention doesn't capitalize abbreviations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
I've now corrected those links. Check it out now. It's about the screenshot of the demo working locally for you, so we know that the function works correctly. You may need to blur out any confidential text in this case. |
Date night tonight, but i should be able to get this worked up over the weekend so we can close this out. |
createAzureOAuthConfig()
createAzureAdOAuthConfig()
and createAzureAdb2cOAuthConfig()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Excellent work. Thank you very much for this. It's a welcome addition.
This would address #115 , i believe i have it working. Haven't tested a lot of different scopes, but it does work with the scopes we are using
https://management.core.windows.net//.default
andopenid
.Here is an example of creating it:
You can also change the prefix used on the environment variables from the default
AZURE
to whatever is needed: