Background
Using Firebase Auth and a SAML Auth Provider with the following configuration:
const config = {
apiKey: "AIzaSy...",
authDomain: "example-app.firebaseapp.com",
};
firebase.initializeApp(config);
const provider = new firebase.auth.SAMLAuthProvider('saml.example-idp');
function saml() {
firebase.auth().signInWithRedirect(provider)
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
}
The CICP configuration for the SAML upstream has the Service Provider: our entity id and the ACS configured as our CICP https://example-app.firebaseapp.com/__/auth/handler.
What I expect to happen
To be able to set a breakpoint in the signInWithRedirect()'s Promise's then and see the authenticated user.
What actually happens
Flow is redirected to the IdP and authentication handled.
The IdP emits the redirect-posting page with automatic submit-on-load and a multipart/form-data form with:
Content-Disposition: form-data; name=SAMLResponse - containing base64
encoded signed SAMLResponse
Content-Disposition: form-data;
name=RelayState - containing the relay state from the SAML flow
Content-Disposition: form-data; name="ssourl" - containing the
firebase project auth handler URI
This in turn causes CICP to render and return a page with script that sets up
var POST_BODY=""------WebKitFormBoundary9bn7AOpnZiIRk9qZ\r\nContent....."
i.e. rather than parsing the form body and extracting the SAMLResponse field, it is replaying the entire Request.body into the script and then calling fireauth.oauthhelper.widget.initialize();
This obviously fails because that roundtrips and then attempts to post the entire response body to the /__/auth/handler endpoint as a querystring.
I suspect there's a simple configuration item that's missing from this chain, because all of the flows look normal to me until the multipart form data gets pushed into the POST_BODY and then prevents the transform of the SAML token into an OAuth token.
The question
What configuration item is incorrect in this (redacted) setup, and what is the correct derivation of the value to replace it with?
Maybe there is also an additional technical issue with SAML, but at least there's a incoherence point in the way sign-in method is used.
According to (Official Docs)[https://cloud.google.com/identity-platform/docs/how-to-enable-application-for-saml#handle-signin-with-client-sdk], you have 2 options to sign-in :
1) With Popup
In this case, you can use a promise to retrieve user credential with sign-in method:
firebase.auth().signInWithPopup(provider)
.then((result) => {
// User is signed in.
// Identity provider data available in result.additionalUserInfo.profile,
// or from the user's ID token obtained via result.user.getIdToken()
// as an object in the firebase.sign_in_attributes custom claim
// This is also available via result.user.getIdTokenResult()
// idTokenResult.claims.firebase.sign_in_attributes.
})
.catch((error) => {
// Handle error.
});
2) With Redirect
In this case, your code should be split into 2 parts.
First sign-in method, without using any promise :
firebase.auth().signInWithRedirect(provider);
Then, the initialization of a "listener", to retrieve user credential after the sign-in redirect :
// On return.
firebase.auth().getRedirectResult()
.then((result) => {
// User is signed in.
// Identity provider data available in result.additionalUserInfo.profile,
// or from the user's ID token obtained via result.user.getIdToken()
// as an object in the firebase.sign_in_attributes custom claim
// This is also available via result.user.getIdTokenResult()
// idTokenResult.claims.firebase.sign_in_attributes.
})
.catch((error) => {
// Handle error.
});
To be added in the bootstrap part of your page/app.
Hope it will help.
Short answer to long question:
The SAML provider handling in Firebase Auth and Google CICP doesn't process multipart/form-data and needs to be in application/x-www-form-urlencoded.
This is a SAML IdP configuration, not something which can be handled by the Firebase Auth service provider configuration.
Related
I am building an application. The client is built with Next.js and the backend with Django and Django REST framework.
In this application, I would like to have social login.
So far, my situation is this.
I have set up the OAuth on the Google dashboard
On the client, I am using next-auth - The client is successfully calling Google and getting an access token from there.
On the client, the callback that runs after getting the access token from Google makes a call my Django API.
I have set up the backend with dj_rest_auth - My settings are almost identical to the ones described here.
Once the client callback runs and calls my Django API with the access token from Google, I successfully get on the client an access token and a refresh token.
If it is a new user loggin in the first time, a new user is created in Djangos DB
const response = await fetch(`${djangoAPIurl}/api/social/login/google/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
access_token: accessToken,
id_token: idToken
})
});
const data = await response.json();
const { access_token, refresh_token } = data;
Both access_token and refresh_token are defined and appear to be valid tokens.
So far, everything happens as expected. My issue appears after this point.
In my api, I have another view defined.
#api_view(['GET'])
#authentication_classes([SessionAuthentication, BasicAuthentication, TokenAuthentication])
#permission_classes([IsAuthenticated])
def test_view(request):
current_user = request.user
print('current_user.auth: ', current_user.is_authenticated)
response = JsonResponse({"received": True})
return response
From my client, I am attempting to call this view in the following way.
const response = await fetch(`${djangoAPIurl}/api/test/test_view/`, {
headers: new Headers({
Authorization: `Bearer ${session.accessToken}`
})
});
The header is constructed correctly, with session.accessToken being the value I got from the api/social/login/google/ call and the request is routed correctly, however, it fails with Forbidden 403 because the user is not authenticated. I have removed the authentication and permission decrators and the request ends up being processed by the view, and there, upon inspection of the user, it is an Anonymous user. I have also tried changing Bearer to Token, to no avail.
Do you have any advice what I might be doing wrong or missing? Have I completely missunderstood how to use the token I get back from api/social/login/google/? All advice is much appreicated!
I think this is because your secret for hashing JWTS on the client side and server side is not same. Next-Auth automatically creates a secret key for hashing jwt's and dj_rest_auth does the same, unless you explicitly tell them both to use the same secret for hashing jwts. I'm a bit late to answer this, but Hope this will help future peoplešš.
I'm using the latest official aws plugin and the flutter graphql plugin. I want to use AppSync. And in the graphql readme it describes how to do this.
To use with an AppSync GraphQL API that is authorized with AWS Cognito
User Pools, simply pass the JWT token for your Cognito user session in
to the AuthLink:
// Where `session` is a CognitorUserSession
// from amazon_cognito_identity_dart_2
final token = session.getAccessToken().getJwtToken();
final AuthLink authLink = AuthLink(
getToken: () => token,
);
My problem is from the aws plugin, There is a method called fetchAuthSession that returns a session with different tokens. But it doesn't return the jwtToken.. Are any of the returned tokens used for AppSync? Please click the session link for the different tokens...
The token that you need for AppSync can be got by the Amplify plugin. It's called accessToken
Future<String> getAcessToken() async {
CognitoAuthSession res = await Amplify.Auth.fetchAuthSession(options: CognitoSessionOptions(getAWSCredentials: true));
final accessToken = res.userPoolTokens.accessToken;
return accessToken;
}
I am using Amazon Cognito for user authentication. After the user is registered verification email is sent to his email address. After clicking on the email link he is prompted with this in his browser.
How can I customize this page in order to insert a script that will trigger deep links within the mobile application, and also make the page look bit nicer?
You can do that using Cognito triggers.
You can configure a trigger template to define a message with a link to a page you control.
The assets will be stored at: amplify/backend/auth/<your-resource-name>CustomMessage/assets
The documentation has more details
Cognito allows you to configure your User Pool to send an email to
your users when they attempt to register an account. You can configure
this email to contain a link to Cognitoās Hosted UI where the userās
account will be marked as confirmed.
This trigger template allows you to define an email message with a
link to a static S3 bucket that you control, where the userās account
will be confirmed and they can then be redirected to a URL of your
choice (presumably your application). The URL will automatically
contain the username as a query string parameters.
Please note that this trigger template will create an S3 resource. The
files that populate the static site are available for edit in
amplify/backend/auth/CustomMessage/assets. They
consist of:
index.html
spinner.js (controls the spinner that appears on the page while users are awaiting confirmation)
style.css
verify.js (the script which performs the verification request)
I was not able to customize the verification page provided by AWS. I created my own UI on my page, which sent the generated code to cognito for verification. For that I needed to:
trigger custom email upon registration
put custom link to verification in the email using the codes provided for the lambda
process the codes on my page
send the codes and username through aws package
Step 1.
In AWS Cognito User Pool, customize workflow with triggers, choose "Custom Message". The triggerSource for verification that I check for are:
event.triggerSource === 'CustomMessage_SignUp' || event.triggerSource === 'CustomMessage_ResendCode'
You can see other trigger sources for CustomMessage here: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-message.html
Step 2. The lambda provides parameters for verification for my users: event.request.userAttributes.sub and event.request.codeParameter. Using these to I constructed a link to my page like so:
https://mypage.com?user_name=${event.request.userAttributes.sub}&confirmation_code=${event.request.codeParameter}
Step 3. On my page, I check if the url params for user_name and confirmation_code are present, and display a modal which is supposed to inform the user if the verification went correctly or not.
Using a package "amazon-cognito-identity-js" I process the code and user_name. First I create the user pool:
import { CognitoUserPool } from 'amazon-cognito-identity-js';
//Aws-cognito credentials
const poolData = {
UserPoolId: YOUR_USERPOOL_ID,
ClientId: YOUR_CLIENT_ID,
};
export default new CognitoUserPool(poolData);
Then to process the code I create a user instance:
import { CognitoUser } from 'amazon-cognito-identity-js';
import UserPool from 'utils/UserPool';
const getUser = () => {
return new CognitoUser({
Username: user_name.toLowerCase(),
Pool: UserPool,
});
};
// After that you can process the code:
getUser().confirmRegistration(code, false, function (err, result) {
if (err) {
if (
err.message === 'User cannot be confirmed. Current status is CONFIRMED'
) {
// Handle already confirmed error
} else {
// Handle other errors you want
}
}
// Handle successful verification
});
The account is verified and you can guide the user to the login page or any other.
When making a request to a flask route that requires a JWT to access using (#jwt_required decorator on flask-restful resources), I get a 422 UNPROCESSABLE ENTITY with the message: The specified alg value is not allowed.
When logging in and navigating to the (frontend) route that calls the request:
this.axios.get("/jobs").then(res => {
console.log(res.data);
this.jobs = res.data.jobs;
});
in the same go, it works as expected, however on refresh it then shows the 422 error.
I store the token in localstorage and load it into axios headers like so:
const api = {
init: function() {
Vue.use(VueAxios, axios);
Vue.axios.defaults.baseURL = DEV_URL;
},
setHeader: function() {
const token = `Bearer ${getToken()}`;
Vue.axios.defaults.headers.common["Authorization"] = token;
},
};
and call init() and setHeader() in my main.js so I am confused why this is causing an error only after a refresh.
I haven't be able to find any resources on how to remedy the The specified alg value is not allowed error. Any assistance would be appreciated! :)
I ran into same problem when the JWT token was created in my spring boot auth service but resource service was a flask micro service. I tried the following steps to sort it out,
I pasted the token in jwt.io Debugger.
On the right hand side I found the decoded header where the alg value was the following,
{
"alg": "HS512"
}
I put the alg in the app config in the flask resource server as follows,
app.config['JWT_ALGORITHM'] = 'HS512'
After that the error message was gone and I was able to parse information from the decoded token. So you need to find the algorithm by which the token was generated and set the appropriate algorithm in the flask app.config.
This can happen if the token was created using a different algorithm then app.config['JWT_ALGORITHM']
Im using version version 1.0.0 of the IdentityServer4 package.
"IdentityServer4": "1.0.0"
I've created a Client
new Client
{
ClientId = "MobleAPP",
ClientName = "Moble App",
ClientUri= "http://localhost:52997/api/",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("SecretForMobleAPP".Sha256())
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api"
},
AllowOfflineAccess = true
}
And the scope/ApiResources
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api", "My API")
};
}
With the following user/TestUser
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "password",
Claims = new []
{
new Claim(JwtClaimTypes.Name, "Bob Smith")
}
}
};
}
I'm trying to test the IdentityServer that I have setup from Postman and determine the possible values for the grant_type key value pair.
I can successfully connect when I set the grant_type to client_credentials and wasn't sure if there were other options for the grant_type value.
Working Postman configuration with grant_type set to client_credentials
Short answer
client_credentials is the only grant_type value you can use directly against the token endpoint when using both hybrid and client credentials grant types.
Longer answer
The client credentials grant type is the only one allowing you to hit the token endpoint directly, which is what you did in your Postman example. In that case the authentication is done against the client itself - i.e. the application you registered.
When you use the hybrid grant type, the authentication will be done against the end-user - the user using your application. In that case, you cannot hit the endpoint token directly but you'll have to issue an authorization request to IdentityServer.
When you do so, you won't use the grant_type parameter but the response_type parameter, to instruct IdentityServer what you expect back.
The possible values for response_type when you use the hybrid grant type can be found in IdentityServer constants - they are the last 3 items in the dictionary:
code id_token, which will return an authorization code and an identity token
code token, returning an authorization code and an access token
code id_token token, giving you back an authorization code, an identity token and an access token
After you get the authorization code, you'll be able to exchange it for an access token and possibily a refresh token by hitting the token endpoint.