Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Sign out functionality #91

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
28 changes: 26 additions & 2 deletions .github/workflows/E2E.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,37 @@ jobs:

- uses: actions/checkout@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.IBERSANO_DOCKER_USERNAME }}
password: ${{ secrets.IBERSANO_DOCKER_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ vars.docker_repo }}/easy-auth-proxy

- name: Build and push Docker images
uses: docker/build-push-action@v3
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

- name: Call the script
continue-on-error: true
run: |
bash main.sh -a "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -c "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -r "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -e ${{ vars.email }} -l ${{ vars.location }}
bash main.sh -i ${{ vars.imageName }} -a "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -c "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -r "${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}" -e ${{ vars.email }} -l ${{ vars.location }}

- name: Delete e2e environment
if: ${{ vars.DeleteOnFailure == 'true' }} || success()
if: ${{ vars.DeleteOnFailure == 'true' }}
run: |
if [ $(az group exists --name ${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }}) == "true" ]; then
az group delete -n ${{ vars.e2ePrefix }}-${{ env.GITHUB_PR_NUMBER }} --yes
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,28 +62,28 @@ jobs:
uses: actions/checkout@v2

- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USER_ID }}
password: ${{ secrets.DOCKER_REGISTRY_PASS }}

- name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
uses: docker/metadata-action@v4
with:
images: |
ghcr.io/${{ github.repository }}/easy-auth-proxy
easyauthfork8s/easy-auth-proxy

- name: Build and push Docker images
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
uses: docker/build-push-action@v3
with:
context: .
push: true
Expand Down
2 changes: 1 addition & 1 deletion AutomationScripts/3-registerAADApp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ if [ -n "$ALT_TENANT_ID" ]; then
fi
fi

CLIENT_ID=$(az ad app create --display-name $AD_APP_NAME --web-home-page-url $HOMEPAGE --web-redirect-uris $REPLY_URLS --required-resource-accesses @./TemplateFiles/manifest.json -o json | jq -r '.appId')
CLIENT_ID=$(az ad app create --display-name $AD_APP_NAME --web-home-page-url $HOMEPAGE --web-redirect-uris $REPLY_URLS --required-resource-accesses @./TemplateFiles/manifest.json --optional-claims @./TemplateFiles/claims.json -o json | jq -r '.appId')
echo "CLIENT_ID: " $CLIENT_ID

# AAD core store is eventually consistent. Usually we can retrieve the object on the first try after creation,
Expand Down
10 changes: 10 additions & 0 deletions TemplateFiles/claims.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"idToken": [
{
"name": "login_hint",
"essential": false
}
],
"accessToken": [],
"saml2Token": []
}
10 changes: 7 additions & 3 deletions charts/easyauth-proxy/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,19 @@ spec:
value: "{{ .Values.easyAuthForK8s.dataProtectionFileLocation }}"
- name: EasyAuthForK8s__SigninPath
value: "{{ .Values.basePath }}{{ .Values.easyAuthForK8s.signinPath }}"
- name: EasyAuthForK8s__SignoutPath
value: "{{ .Values.basePath }}{{ .Values.easyAuthForK8s.signoutPath }}"
- name: EasyAuthForK8s__AuthPath
value: "{{ .Values.basePath }}{{ .Values.easyAuthForK8s.authPath }}"
- name: EasyAuthForK8s__AllowBearerToken
value: "{{ .Values.easyAuthForK8s.allowBearerToken }}"
- name: EasyAuthForK8s__DefaultRedirectAfterSignin
value: "{{ .Values.easyAuthForK8s.defaultRedirectAfterSignin }}"
name: EasyAuthForK8s__CompressCookieClaims
- name: EasyAuthForK8s__DefaultRedirectAfterSignout
value: "{{ .Values.easyAuthForK8s.defaultRedirectAfterSignout }}"
- name: EasyAuthForK8s__SignedOutNoRedirectPath
value: "{{ .Values.basePath }}/signedout"
- name: EasyAuthForK8s__CompressCookieClaims
value: "{{ .Values.easyAuthForK8s.compressCookieClaims }}"
- name: EasyAuthForK8s__ResponseHeaderPrefix
value: "{{ .Values.easyAuthForK8s.responseHeaderPrefix }}"
Expand All @@ -81,8 +87,6 @@ spec:
value: "{{ .Values.azureAd.clientId }}"
- name: AzureAd__CallbackPath
value: "{{ .Values.basePath }}{{ .Values.azureAd.callbackPath }}"
- name: AzureAd__SignedOutCallbackPath
value: "{{ .Values.basePath }}{{ .Values.azureAd.signedOutCallbackPath }}"
- name: AzureAd__SignUpSignInPolicyId
value: "{{ .Values.azureAd.signUpSignInPolicyId }}"
- name: AzureAd__ClientSecret
Expand Down
24 changes: 17 additions & 7 deletions charts/easyauth-proxy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

tlsSecretName: ""
appHostName: ""

# The base path will be pre-pended to all urls on the EasyAuth Proxy
# For example, the Auth endpoint will listen on "/easyauth/auth" by default
# You should change the base path if you are deploying multiple EasyAuth pods
# that share the same host name, such as a mult-tenant app. See documentation
# for more.
basePath: "/easyauth"

replicaCount: 2
Expand Down Expand Up @@ -98,28 +104,32 @@ azureAd:
tenantId: ""
# app Id of the service principal.
clientId: ""
# B2C Sign-in policy, if used
# Leave this blank if not using B2C
signUpSignInPolicyId: ""
# configure paths for OIDC middleware
# there's no reason to change these unless there is a conflict
# such as another easyauth proxy using the same host name
# you shouldn't need to change these from the default
# all paths will be prefiixed with the basePath value
callbackPath: "/signin-oidc"
signedOutCallbackPath : "/signout-callback-oidc"
# Leave this blank if not B2C
signUpSignInPolicyId: ""

easyAuthForK8s:
# data protection key ring location
dataProtectionFileLocation: "/mnt/dp"
# configure paths for EasyAuth middleware
# there's no reason to change these unless there is a conflict
# such as another easyauth proxy using the same host name
# you shouldn't need to change these from the default
# all paths will be prefiixed with the basePath value
signinPath: "/login"
authPath: "/auth"
signoutPath: "/logout"
# use bearer token as a fall back for cookies
# normally for API web applications only
allowBearerToken: "false"
# fallback path to redirect user after signin if
# prior page url cannot be determined
defaultRedirectAfterSignin: "/"
# fallback path to redirect user after signout
# if no redirect parameter is provided
defaultRedirectAfterSignout: "_blank"
# Make the cookie payload as small as possible to avoid having to
# increase the allowed nginx header size.
compressCookieClaims: "true"
Expand Down
9 changes: 6 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ Here's a list of possible configuration options for the EasyAuth Proxy, which yo
| azureAd | domain | Optional. If your users are internal organizational accounts from a single tenant domain, this can be helpful by providing a "hint" during login to help ensure that the user logs in with the appropriate user account|
| azureAd | tenantId | If you are using the setup script, this value will be determined at runtime and filled in for you. Otherwise, this is the GUID tenant identifier for the Azure AD tenant you want to use. See [How to find my tenant id](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant)|
| azureAd | clientId | If you are using the setup script, this value will be determined at runtime and filled in for you. Otherwise, this is the GUID application identifier for the Azure AD app registration you want to use. See [App Registrations](https://docs.microsoft.com/en-us/graph/auth-register-app-v2)|
| azureAd | callbackPath | The path that Open Id Connect messages will be returned from Azure AD. In the majority of cases, you should never need to change this. See [Advanced Scenarios](docs/scenarios.md)|
| azureAd | signedOutCallbackPath | Reserved for future use - Not currently used|
| azureAd | signUpSignInPolicyId | For B2C only. This is the name of the policy that should be used. Otherwise, leave blank.|
| azureAd | callbackPath | The path that Open Id Connect messages will be returned from Azure AD. In the majority of cases, you should never need to change this. This configurationoption may be removed in the future. See [Advanced Scenarios](docs/scenarios.md)|
| azureAd | signedOutCallbackPath | The path that the user will be redirected after clearing the session with Azure AD. It is not recommended that you change this. This configuration option may be removed in the future. See [Advanced Scenarios](docs/scenarios.md)|
| azureAd | clientSecretKeyRefName, clientSecretKeyRefKey | Secret container and key for the client secret. Do not change these or set them directly or store the secret in a yaml file. Rather, provide your secret to helm via the command line via *--set secret.azureclientsecret=$CLIENT_SECRET* |
| easyAuthForK8s | dataProtectionFileLocation | data protection key ring location. |
| easyAuthForK8s | signinPath | The path that the proxy host will respond to sign-in requests. The default should not need to be changed, except for in [Advanced Scenarios](docs/scenarios.md). Note that when changing this value, you must also update the *nginx.ingress.kubernetes.io/auth-signin* annotation in your ingresses to match. |
| easyAuthForK8s | signoutPath | The path that the proxy use to sign out a user. The default should not need to be changed, except for in [Advanced Scenarios](docs/scenarios.md). |
| easyAuthForK8s | authPath | The path that the proxy host will respond to auth requests. The default should not need to be changed, except for in [Advanced Scenarios](docs/scenarios.md). Note that when changing this value, you must also update the *nginx.ingress.kubernetes.io/auth-url* annotation in your ingresses to match. |
| easyAuthForK8s | allowBearerToken | Default is "false". If "true" this will allow bearer tokens to be used in addition to cookies. Primarily for API callers. |
| easyAuthForK8s | defaultRedirectAfterSignin | This is the final fallback url that the user will be routed to after succesfully logging in. Depending on your nginx configuration, the primary redirect preference will be the path provided by the "rd" query parameter, followed by the url that the user was originally trying to access. This option provides a tertiary and final fallback, with "/" being the default |
| easyAuthForK8s | defaultRedirectAfterSignout | This is the fallback url that the user will be routed to after logging out. This should be a page that allows anonymous access, otherwise it will result in another log in challenge that results in the user being signed in again. If you don't have page that allows anonymous access, you can remove this variable or set it to a value of "_blank" to configure EasyAuth to render a basic page for you. |
| easyAuthForK8s | compressCookieClaims | Option is "true" by default, set "false" to disable. Experimental feature that serializes, compresses, and encodes the payload of non-essential claims to keep the cookie size as small as possible. This helps to avoid increasing the nginx header buffers beyond the default settings and reduces the size of the data sent from the client with each request. <br/><br/>**WARNING!**: *This feature may introduce a security vulnerability, although no specific vulnerability is known at this time. CRIME, a well-known exploit, takes advantage of compressed streams to decrypt data, however it requires the attacker to be able to introduce arbitrary data into the stream and observe its compressed state. For this feature we are only compressing a portion of the payload which an attacker should not be able to manipulate, so it should be safe in theory. To mitigate any potential concerns, avoid sending sensitive data to the back-end service, or disable this feature.* |
| easyAuthForK8s | responseHeaderPrefix | Prefix for all user information headers. Default is *"x-injected-"*. There is no reason to change this unless you have multiple EasyAuth proxies protecting the same backend and need to discern the source of the headers. |
| easyAuthForK8s | claimEncodingMethod | Default is *UrlEncode*, which should work for most situations. Valid values are: <ul><li>*UrlEncode*: Invalid characters are escaped according to IETF RFC 3986</li><li>*Base64*: The full string value is encoded from UTF-8 bytes to base64 text</li><li>*None*: Value is not encoded, and the original string value is sent. This may cause errors for downstream web servers, especially on older platforms</li><li>*NoneWithReject*: No encoding is applied, but any header value containing an unsafe character is rejected, and the value "encoding_error" is sent in its place</li></ul> |
Expand Down Expand Up @@ -81,4 +83,5 @@ Notes:
- Not all graph queries work with all types of users, since many graph resources are dependent on the various product licenses that are assigned to the user. If a query raises errors, an `error` property will be returned along with a message.
- Finally, graph queries are run against the Azure AD tenant that EasyAuth is configured to use, which is not necessarily a particular user's home tenant. For example, let's say EasyAuth is configured to use the "Contoso" tenant, which contains a B2B Guest user from the "Fabrikam" tenant. You utilize a graph query that looks for groups users belong to. In this case the results returned for the B2B user will be their "Contoso" group memberships, not groups they belong to in their home "Fabrikam" tenant.


# Implementing Sign-out functionality for your protected applications
For web applications, EasyAuth can sign a user out of both the cookie session and Azure AD. To sign out a user, you'll need to redirect them to the `signoutPath`, for which the default value is `"/easyauth/logout"`, by providing a link or a button in your application. You may optionally provide a url within your application to return them to after signing out by either setting the `defaultRedirectAfterSignout` value in the helm chart, or by setting the `rd` query string parameter. An example of a link you direct the user to my look like `"/easyauth/logout?rd=/signedout.html"`. If you don't provide a redirect url by setting either of these options, EasyAuth will render a basic page for you.
41 changes: 40 additions & 1 deletion docs/scenarios.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,41 @@
# Advanced Scenarios
Yikes! We haven't had time to complete this doc yet. We are working on it, so check back later for some interesting ways to configure EasyAuth.

## Multi-tenant apps
For applications that need to support multiple Azure AD tenants independently, you can configure and deploy multiple EasyAuth pods. As long as you can distinguish different tenants with ingress rules, you will be able to route auth requests to the correct pod.

For example, let's say you have an application with the url "https://mysharedapp.constoso.com/". This app is a multitenant evironment, where the base url path identifies the tenant within the application ("https://mysharedapp.constoso.com/fabrikam). Configure the helm chart values of each EasyAuth pod with a unique `basePath`, so that the ingress rules can route auth requests to the correct pod. Assuming we use "fabrikam" as the basePath for our sample tenant, your ingress configuration would look something like:

```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: easyauth-fabrikam-tenant
annotations:
nginx.ingress.kubernetes.io/auth-url: "https://$host/fabrikam/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://$host/fabrikam/login"
nginx.ingress.kubernetes.io/auth-response-headers: "x-injected-userinfo,x-injected-name,x-injected-oid,x-injected-preferred-username,x-injected-sub,x-injected-tid,x-injected-email,x-injected-groups,x-injected-scp,x-injected-roles,x-injected-graph"
cert-manager.io/cluster-issuer: {{your-cert-manager}}

spec:
ingressClassName: nginx
tls:
- hosts:
- {{APP_HOSTNAME}}
secretName: {{TLS_SECRET_NAME}}
rules:
- host: {{APP_HOSTNAME}}
http:
paths:
- path: /fabrikam
pathType: Prefix
backend:
service:
name: mysharedapp-pod
port:
number: 80
```


You will also need to update your Azure AD App Registration (or create a new one) to include the OIDC reply url for the fabrikam EasyAuth pod. The url will be in the form of `https://host/{{baseUrl}}/{{azureAd.callbackPath}}`, which in this case would be "https://mysharedapp.constoso.com/fabrikam//signin-oidc". See [Add a redirect URI](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#add-a-redirect-uri) for more information.

Finally, you will need to update the helm chart values to reflect fabrikam's Azure AD tenant settings. At a miminum, you'll need to set `azureAd.tenantId` to the GUID Id of fabrikam's Azure AD tenant, as well as the `azureAd.domain` value (not required, but provides the best user experience). If you are sharing the same App Registration among EasyAuth pods, the `clientId` value will be the same. In all cases where the App Registration is configured in a tenant that is different than the `azureAd.tenantId` value, you'll need to ensure that the App Registraion is [Multitenant](https://learn.microsoft.com/en-us/azure/active-directory/develop/single-and-multi-tenant-apps).
2 changes: 1 addition & 1 deletion sample/EasyAuthForK8s.Sample/Pages/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<a class="nav-link" href="/Graph">Graph Query</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/Anonymous" onclick="alert('Please open a GitHub issue if you need a sign out example.')">Sign Out</a>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/Anonymous is a valid path that we should keep. I'm not sure why it was labeled as "Sign-Out", but we should keep it as <a class="nav-link" href="/Anonymous">Anonymous</a> and Sign-out would be a separate nav link.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a button on line 26 for Anonymous. I've got one for signout and one for Anonymous

<a class="nav-link" href="/easyauth/logout" >Sign Out</a>
</li>
</ul>
</div>
Expand Down
1 change: 0 additions & 1 deletion sample/templates/sample-ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ spec:
name: easyauth-sample-pod
port:
number: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
Expand Down
9 changes: 9 additions & 0 deletions src/EasyAuthForK8s.Web/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,19 @@ public class Claims
public const string Name = "n";
public const string Subject = "s";
public const string Role = "r";
public const string LoginHint = "h";
}
//non-standard claims that AAD supports for OIDC
public class AadClaimParameters
{
public const string LoginHint = "login_hint";
public const string LogoutHint = "logout_hint";
}

public static readonly string[] IgnoredClaims = {
"aud","iss","iat","idp","nbf","exp","c_hash","at_hash","aio","nonce","rh","unique_name","uti","ver"
};

public const string NoOpRedirectUri = "_blank";
}

Loading