Photon Secret Key is visible in Error Log?

When I send an Error Code with a Non-0 value via web hook the room fails to be created, which is in line with the documentation.

However, I see that the player logs store the secret key. I tried turning off ErrorInfo and other log level shenanigans but it is still visible, even in a standalone, non debug build.

How can I prevent a user from seeing this key?
I asked the question here along with some others:

https://community.playfab.com/questions/44938/is-there-an-updated-guide-for-async-photon-unity-t.html

Thanks in advance.

Best Answer

«1

Answers

  • JohnTube
    JohnTube ✭✭✭✭✭
    Hi @AllFatherGray,

    I just replied on PlayFab forums.
    How can I prevent a user from seeing this key?
    You can't avoid exposing PlayFab's Secret Key in client logs currently.
  • @JohnTube said:
    Hi @AllFatherGray,

    I just replied on PlayFab forums.(Quote)
    You can't avoid exposing PlayFab's Secret Key in client logs currently.

    Thanks for tge response, I asked another question in that forum about names and the key.
    Could you please give it a look?

  • JohnTube
    JohnTube ✭✭✭✭✭
    I think there is a limit to the number of the nested/chained replies in the PlayFab forums, I can't reply to your last comment/reply, so let's continue here since it's about Photon anyway and this Vanilla forum is more convenient and easy to use.
    Here is a link to your last questions for reference.
    Is there a way to force webhooks? I don't see how I can enforce rules on a user that can avoid the server logic.
    No there is none. After all with or without webhooks forwarding for SetProperties or RaiseEvent you can't cancel or prevent the operation from PlayFab CloudScript or any other WebHooks endpoints implementation.
    What work-arounds are available for the Secret Key?
    Can you get someone to explain it?

    Because right now it seems like a person could read the logs and send calls outside of the ecosystem we are developing. Unless I am missing something this seems like a big issue. From my understanding, a person could:

    1. Download a game that is using Photon Unity.
    2. Trigger an error in the Path webhooks (Like in PathCreate).
    3. Obtain the secret-key from the player.log.
    4. Make calls to the Server using the key.

    Is this okay?

    First I'm here (the "someone") to explain all this and answer any question about it.
    Photon offers a way to protect from this using AuthCookie: the authentication provider returns a secure object per user, this object is sent in PathCreate, PathJoin automatically and always and can be sent on demand by client request in webhooks per WebFlag.SendAuthCookie for PathGameProperties, PathEvent and PathLeave. This can be used in SetProperties, RaiseEvent and LeaveRoom client methods calls.
    Unfortunately PlayFab does not offer this out of the box but you can make it work with PlayFab nevertheless. I tested it myself.
    See here for more info (this is not officially supported).
  • AllFatherGray
    AllFatherGray ✭✭
    edited September 2020
    JohnTube wrote: »

    First I'm here (the "someone") to explain all this and answer any question about it.
    Photon offers a way to protect from this using AuthCookie: the authentication provider returns a secure object per user, this object is sent in PathCreate, PathJoin automatically and always and can be sent on demand by client request in webhooks per WebFlag.SendAuthCookie for PathGameProperties, PathEvent and PathLeave. This can be used in SetProperties, RaiseEvent and LeaveRoom client methods calls.
    Unfortunately PlayFab does not offer this out of the box but you can make it work with PlayFab nevertheless. I tested it myself.
    See here for more info (this is not officially supported).

    Before I continue, Again, thanks you've been a great help
    So you are saying that I should call this CustomAuth function in PathCreate and the other methods?
    And then I compare the returned auth to the one in the args?

    For example:
    PathCreate(args)
    {
    var result = CustomAuth(args);
    //compare here?
    }
    I notice you have the method connected to handler.CustomAuth, could I remove the handler variable and keep this method for server side checks? So, handlers.CustomAuth -> CustomAuth

    Lastly I asked about the name in the playfab discussion (below):

    > When joining a room in photon if the player name is the same as the player who created the room will photon permit the join or block it? I believe that when you’re not authenticating with photon it will be blocked, but does that behavior persist after we obtain a photon auth token?

    Must I force unique names or will the auth be enough?
    Or should I combine them for unique names like this: {Username}{PlayfabID} to make each name unique?

    EDIT:
    My email for the forum and actual PUN account don't match and I don't get notifications.
    How do I change this forums email?
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited September 2020
    So you are saying that I should call this CustomAuth function in PathCreate and the other methods?
    And then I compare the returned auth to the one in the args?
    No I'm saying you should use a different way to use custom authentication with PlayFab, different from the official integration.
    So instead of URL format:
    https://{PlayFabTitleId}.playfabapi.com/photon/authenticate
    use:
    https://{PlayFabTitleId}.playfablogic.com/webhook/1/prod/{PhotonSecretKey}/CustomAuth
    and from the client:
    AuthenticationValues authValues = new AuthenticationValues();
    authValues.AuthType = CustomAuthenticationType.Custom;
    authValues.AddAuthParameter("UserId", userId); // PlayFabId
    authValues.AddAuthParameter("AppId", appId); // Photon AppId
    authValues.AddAuthParameter("Ticket", ticket); // PlayFab auth session ticket
    authValues.AddAuthParameter("Token", token); // Photon auth token obtained from PlayFab via client API call GetPhotonAuthenticationToken
    authValues.AddAuthParameter("AppVersion", appVersion); // Photon AppVersion
    
    and do not forget to include the CustomAuth handler in your CloudScript revision.
    The idea behind this is to use a custom WebHooks/WebRPC endpoint for custom auth.
    So the custom handler "CustomAuth" (you may rename handler and update URL accordingly) will be called by Photon Server when doing authentication instead of the normal endpoint.
    And inside CustomAuth handler we do the actual authentication and more (get user info, compare AppVersion, return AuthCookie, return Data, etc.)
    You don't have to do all this, I shared the code I had in full but what you need is just AuthCookie and you decide whatever sensitive/secret data per user you put in there to make sure WebHooks calls are incoming from a trusted user and not some hacker.
    > When joining a room in photon if the player name is the same as the player who created the room will photon permit the join or block it? I believe that when you’re not authenticating with photon it will be blocked, but does that behavior persist after we obtain a photon auth token?

    Must I force unique names or will the auth be enough?
    Or should I combine them for unique names like this: {Username}{PlayfabID} to make each name unique?
    I think this is more than one question/thing. So I will try to clarify each point separately:
    - when you authenticate using PlayFab the Photon UserId will be set to the PlayFabId. So the authentication is enough and will do this for you.
    - by default each actor joined to a room must have a unique UserId, so if a user joins a room with UserId X no other user can join the same room with the same UserId X. For example if someone connects with the same account on multiple devices he can connect to Photon but cannot join the same room from more than one device.
    My email for the forum and actual PUN account don't match and I don't get notifications.
    How do I change this forums email?
    Send an email to developer@photonengine.com from the account you want to change (current forum account) and include the new email that you want to use for the forum to match the account where you have PUN app.
  • Just to confirm,, If I authenticate using the above method I shouldn't have to worry about the exposed secret key? I guess I am asking what is different from getting an auth key the other way?
    How does this auth key differ?

    Ah, so the names can match because of the user ID being different? Good to know.

    I will contact the email to change my address, thanks.

  • JohnTube
    JohnTube ✭✭✭✭✭
    If I authenticate using the above method I shouldn't have to worry about the exposed secret key? I guess I am asking what is different from getting an auth key the other way?
    How does this auth key differ?
    When you use this method you can return AuthCookie. When you properly use AuthCookie you can make sure, from PlayFab webhooks handlers (except PathClose) that the webhook is safe (not triggered via someone who managed to get the WebHooks URL and secret key for your title).
    So you still need to worry as you can't prevent WebHooks URL or secret key from leaking but you asked for a workaround.
    The method I suggest is a wrapper around default approach suggested in official integration which adds missing features, AuthCookie being one of those.
    Ah, so the names can match because of the user ID being different? Good to know.
    I'm having trouble following. Are you asking about Photon Nickname or UserId? Nickname are free and there is no constraint. UserId, only one is allowed per room: all actors need to have different UserIds. You can't join a room if there is an actor with the same UserId already joined. This does not apply to Nicknames.
  • AllFatherGray
    AllFatherGray ✭✭
    edited September 2020
    Yes I was talking about both the nickname and userid. I wanted to know how player uniqueness was done.
    Thanks for clarifying it.

    So how would I call this CustomAuth Method? Do I use Execute cloud script in the client?
    Do I send the AuthenticationValues object as the arg? What is AuthenticationValues?
    Where would I call the CustomAuth() method down below?
     /*
         * Step 3
         * This is the final and the simplest step. We create new AuthenticationValues instance.
         * This class describes how to authenticate a players inside Photon environment.
         */
        private void AuthenticateWithPhoton(GetPhotonAuthenticationTokenResult obj)
        {
            LogMessage("Photon token acquired: " + obj.PhotonCustomAuthenticationToken + "  Authentication complete.");
    
            //We set AuthType to custom, meaning we bring our own, PlayFab authentication procedure.
            var customAuth = new AuthenticationValues { AuthType = CustomAuthenticationType.Custom };
            //We add "username" parameter. Do not let it confuse you: PlayFab is expecting this parameter to contain player PlayFab ID (!) and not username.
            customAuth.AddAuthParameter("username", _playFabPlayerIdCache);    // expected by PlayFab custom auth service
    
            //We add "token" parameter. PlayFab expects it to contain Photon Authentication Token issues to your during previous step.
            customAuth.AddAuthParameter("token", obj.PhotonCustomAuthenticationToken);
    
            //We finally tell Photon to use this authentication parameters throughout the entire application.
            PhotonNetwork.AuthValues = customAuth;
        }
    
    
  • Ok I took a look at the doc and your earlier post and it seems that I will receive the cookie when I log on once Photon attempts to call the custom auth url. So I don't call CustomAuth myself, correct?

    So, once the call is finished and I return the cookie, is the Cookie automatically set in the client?

    "AuthCookie: also called secure data, is a JSON object returned by the web service but will not be accessible from client side as it will be embedded in the encrypted token received. It could be sent later with Webhook or WebRPC HTTP requests."

    I saw this in the document, can you clarify it? Does it mean the cookie will be embeded in the args.Token?
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited September 2020
    Hi @AllFatherGray,
    Ok I took a look at the doc and your earlier post and it seems that I will receive the cookie when I log on once Photon attempts to call the custom auth url. So I don't call CustomAuth myself, correct?
    Correct, just replace Auth URL in the dashboard and Photon Server (not the client) will automatically call the handler when the client connects with proper authentication values (type and parameters) configured.
    I saw this in the document, can you clarify it?
    The client does not need to know the AuthCookie as the whole point of this is to not trust the client. Only the server saves this and will resend it to your web server in some webhooks always/automatically and for some webhooks it requires the client to initiate that via webflag.
    Does it mean the cookie will be embeded in the args.Token?
    No. There is no default args.Token in webhooks or webRPCs. args.Token in the suggested workaround inside CustomAuth handler is what the client sends, which is the actual Photon auth token via PlayFab client API call (you can rename it to whatever you want btw, it's a query string parameter key name).
  • @JohnTube
    Hello, hope you had a good weekend.
    The client does not need to know the AuthCookie as the whole point of this is to not trust the client. Only the server saves this and will resend it to your web server in some webhooks always/automatically and for some webhooks it requires the client to initiate that via webflag.

    So I simply return a unique string as AuthCookie on this line? Where secret is in the line below? The cookie is saved in the Photon Server?
    return { ResultCode: 1, UserId: userId, Nickname: nickname/*, Data: userInfo*//*, AuthCookie: secret*/ };
    

    From that point onwards, do I just check 'args.AuthCookie' on all subsequent wehook calls?
    If so, what do I check for?

    I see that args.Ticket is used in the CustomAuth, how do I get that down below?
    customAuth.AddAuthParameter("ticket", /* What goes here from playfab? */);

    Because I see this:

    // authValues.AddAuthParameter("Ticket", ticket); // PlayFab auth session ticket

    Where do I get that from?

    Thanks in advance.
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited September 2020
    So I simply return a unique string as AuthCookie on this line?
    not a string but a JSON object with one level depth (no nested objects).
    example:
    var secret = { "key": "value", "k": 1, "kk": true };
    
    Where do I get that from?
    string ticket = PlayFabSettings.staticPlayer.ClientSessionTicket
    customAuth.AddAuthParameter("ticket", ticket);
    
  • AllFatherGray
    AllFatherGray ✭✭
    edited September 2020
    @JohnTube

    JohnTube wrote: »
    So I simply return a unique string as AuthCookie on this line?
    not a string but a JSON object with one level depth (no nested objects).
    example:
    var secret = { "key": "value", "k": 1, "kk": true };
    


    Do I ever check for the cookie explicitly in the webhooks? Or is it autochecked in the server calls?
    And what should I put in the secret - a random key?
    Would the below code work as an acceptable AuthCookie/secret?
    var secret = { "key": "some random GUID" };
    

    Lastly,
    Does the cookie expire? I know Playfab tickets expire in 24 hours. should I expect the same for the cookie?
  • JohnTube
    JohnTube ✭✭✭✭✭
    Do I ever check for the cookie explicitly in the webhooks? Or is it autochecked in the server calls?
    And what should I put in the secret - a random key?
    You do the check yourself in each CloudScript handler, if AuthCookie is expected but missing or if it's there but its value is wrong then assume this webhook was not sent by Photon Server or was sent using a malicious client (through Photon Server, the latter is probably a modified client that does not set proper webflag).
    Would the below code work as an acceptable AuthCookie/secret?
    yes sure. it should be something per user, per session and that you can keep track of from CloudScript to compare it to with received values.
    Does the cookie expire? I know Playfab tickets expire in 24 hours. should I expect the same for the cookie?
    AuthCookie is per session. It's gone when the client is disconnected from Photon Servers. But it does not expire otherwise.
  • @JohnTube
    You do the check yourself in each CloudScript handler, if AuthCookie is expected but missing or if it's there but its value is wrong then assume this webhook was not sent by Photon Server or was sent using a malicious client (through Photon Server, the latter is probably a modified client that does not set proper webflag).

    Ah ok, so they can't see the cookie because it's saved on the server but it is sent anyways.
    But I should still check the secret to validate.If they set webflags to false the AuthCookie won't be sent, correct? In that case I would also check if the AuthCookie is null/undefined?

    And when you mean do a check you mean we must validate the Secret any way we want?

    Here is an example to make sure I have it right:
    A user gets an AuthCookie from us and we save a copy of the cookie some whereelse.
    Photon saves the cookie as well.

    When a legal user calls:
    We compare the auth cookie to our store to the one Photon re-sent.

    Okay here are the different outcomes:

    1.
    If a hacker sends a fake cookie:
    The Cookie will not match or be invalid.

    2.
    If hacker makes a call to the url without a fake cookie:
    the AuthCookie is undefined or missing so we ban/penalty user.

    3.
    If a hacker set webflag to false (like in (SetRoomProperties or RaiseEvent):
    the AuthCookie is undefined or missing so we ban/penalty user.

    All of this is possible because the AuthCookie is never sent to the client but is actually held onto the Photon Server. So we only expect args.AuthCookie to match if it's still a valid cookie with what we stored.

    Are the 3 examples and the above correct?


    Lastly, How would you reccomend I store/check the Cookie?
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited September 2020
    But I should still check the secret to validate.If they set webflags to false the AuthCookie won't be sent, correct? In that case I would also check if the AuthCookie is null/undefined?
    Correct, yes.
    And when you mean do a check you mean we must validate the Secret any way we want?
    It means you compare and check in your code according to how AuthCookie format/structure is and whether or not it's expected/used in all webhooks where applicable.
    Are the 3 examples and the above correct?
    Yes. When cookie is not found or invalid, I would not be so sure to punish the user as someone else may be using a fake/someone else's UserId/PlayFabId. So I would only log this, report it and ignore any extra logic/processing.
    Lastly, How would you reccomend I store/check the Cookie?
    It's been a long time since I used PlayFab and it has change a lot. Player Data (Title) maybe?

    Just a note/reminder:
    PathClose cannot have an AuthCookie, so this one you should check validity by making sure all the args are there as expected and their values are valid. Then see if the GameId closed is currently marked as open/ongoing.

    Recommendation:
    I would change ALL handlers names (respectively webhooks' pathes) EXCEPT PathClose.
    This way if some hacker knows Photon/PlayFab at least you can hope he does not guess all webhook paths by using default handers' names.
  • @JohnTube
    Then see if the GameId closed is currently marked as open/ongoing.

    Ok, how do I check if the GameID is open/closing? Is there an API call to do this?
  • AllFatherGray
    AllFatherGray ✭✭
    edited September 2020
    @JohnTube Thank you so much! This thread clears up most of my concerns.
    If I have anymore questions I will ask again but this is good for now.
    count the active actors (it should be zero).
    Oh I guess the actorlist is contained in the state or is it in game.args?
    Or you mean to check what is in the sharedgroup data?

    EDIT:
    I just thought of something!
    How can I enforce the room names for the CreateRoom?

    Would I have to resort to fetching and checking preapproved GUIDs from a secure source?

    EX:

    In the cloud I save the 'next' room name for a user.
    When they attempt to make a room they must first ask for this new name.
    If the room they attempt to create with args.Type == Create is different from the value, I throw an error.
    After that, I save a new roomname for the next create that user might call.
  • JohnTube
    JohnTube ✭✭✭✭✭
    Or you mean to check what is in the sharedgroup data?
    Yes.
    For room names simply do not set one (send null) in CreateRoom method and let Photon Server generate a GUID for you.
    If you want to use custom room names see this.
  • AllFatherGray
    AllFatherGray ✭✭
    edited September 2020
    @JohnTube
    For room names simply do not set one (send null) in CreateRoom method and let Photon Server generate a GUID for you.

    I was talking about the case where a user sets the name purposefully like:
    CreateRoom("fakename");/// they removed null and put this instead.
    So the call is legal but the name is what I am worried about.

    My example of how to solve it is:

    Would I have to resort to fetching and checking preapproved GUIDs from a secure source?

    EX:

    In the cloud I save the 'next' room name for a user.
    When they attempt to make a room they must first ask for this new name.
    If the room they attempt to create with args.Type == Create is different from the value, I throw an error.
    After that, I save a new roomname for the next create that user might call.

    Would the above be an acceptable way to keep the names under my control?
  • @JohnTube

    I also forgot to ask:

    Are there any plans to integrate the gits or workarounds you made into an official format?

  • JohnTube
    JohnTube ✭✭✭✭✭
    Would I have to resort to fetching and checking preapproved GUIDs from a secure source?
    Would the above be an acceptable way to keep the names under my control?
    So you want to make sure the client calling CreateRoom is from genuine client? Or you want to allow custom room names no matter what?
    In either cases I think you now have enough knowledge about how all this works and there are enough features to figure out.
    Are there any plans to integrate the gits or workarounds you made into an official format?
    No sorry. At some point we will have an updated webhooks version but no ETA and not soon.
  • @JohnTube

    Yes I meant something else.
    Like a is genuine but alters the name of the create room request from null to something else.
    So they correctly login but maliciously change the code and
    CreateRoom(null) turns into -> CreateRoom("really different name")

    So my suggestion was to generate a name for a user on the server and save it and then send it to them and only let a create room request succeed if the name/gameid of the room matched the name I generated on the server. Does that sound like a good approach? I swear this is my last chain of questions for now!
    No sorry. At some point we will have an updated webhooks version but no ETA and not soon.

    Okay, I'll just keep looking at the news from Photon.

    Again, thank you so much for answering all the questions these past few weeks!
  • @JohnTube

    I switched my auth to the URL you described earlier and I get this error now:
    OperationResponse 230: ReturnCode: 32755 (Custom authentication got empty response string.). Parameters: {} Server: NameServer Address: ns.exitgames.com:5058
    UnityEngine.Debug:LogError(Object)
    Photon.Realtime.LoadBalancingClient:DebugReturn(DebugLevel, String) (at Assets/Photon/PhotonRealtime/Code/LoadBalancingClient.cs:2393)
    Photon.Realtime.LoadBalancingClient:OnOperationResponse(OperationResponse) (at Assets/Photon/PhotonRealtime/Code/LoadBalancingClient.cs:2467)
    ExitGames.Client.Photon.PeerBase:DeserializeMessageAndCallback(StreamBuffer)
    ExitGames.Client.Photon.EnetPeer:DispatchIncomingCommands()
    ExitGames.Client.Photon.PhotonPeer:DispatchIncomingCommands()
    Photon.Pun.PhotonHandler:Dispatch() (at Assets/Photon/PhotonUnityNetworking/Code/PhotonHandler.cs:208)
    Photon.Pun.PhotonHandler:FixedUpdate() (at Assets/Photon/PhotonUnityNetworking/Code/PhotonHandler.cs:142)
    

    It works when I put the auth URL back to:

    https://{PlayFabTitleId}.playfabapi.com/photon/authenticate

    Is there something I missed?
  • JohnTube
    JohnTube ✭✭✭✭✭
    Is there something I missed?
    according to the error message:
    Custom authentication got empty response string.
    it looks like you need to check the response.
    you could do it from a browser or using Postman to test that it works as expected.
  • @JohnTube

    For reference I am using this git:
    https://gist.github.com/JohnTube/122568b1183db131d50f5f17935ee21a
    it looks like you need to check the response.
    you could do it from a browser

    How do I check it from the browser?
  • JohnTube
    JohnTube ✭✭✭✭✭
    edited October 2020
    forget the browser as it's not sending POST. I apologize about mentioning it.

    please use postman to fake the request the Photon Server is going to send to PlayFab and make sure you have a returned JSON response as expected by Photon.

    use the URL of webhooks + handler name.
    example:
    https://{{PlayFabTitleId}}.playfablogic.com/webhook/1/prod/{{PhotonSecretKey}}/CustomAuth
    send POST with payload, example (replace values and add missing for test)
    {
       AppId: "<photonAppId>",
       UserId: "<userId>",
    }
    
    you should get a JSON response like, example:
    {
       ResultCode: 2 
       Message: "Token is undefined"
    }
    
    this is how I test it (values between {} are replaced as they are environment variables)
    1QSgDLZ.png
  • @JohnTube
    So I tried making a post request to https://{{PlayFabTitleId}}.playfablogic.com/webhook/1/prod/{{PhotonSecretKey}}/CustomAuth with the body as you suggested and received
    {
        "code": 500,
        "status": "InternalServerError",
        "error": "InternalServerError",
        "errorCode": 1110,
        "errorMessage": "An unexpected error occured while processing the request.",
        "errorHash": "5545bfb177b6874beb917291c6901c7d",
        "errorDetails": {
            "RequestID": [
                "b6d2cc8e4c844b4e90a2f4b0e740f2a5"
            ]
        }
    }
    
  • JohnTube
    JohnTube ✭✭✭✭✭

    Make sure to use POST not GET and to send body content as raw, JSON
    and also format message properly it should be proper valid correct JSON.
    see the screenshot in my previous message.