4

I am trying to implement the use of an API service which requires JWT authentication for all its API calls.

I understand what JWT tokens are and how they are used, my issue is that I am writing a Swift app and can't quite figure out the process to generate the token so that I can attach it as a Bearer in my API calls.

  • Can I generate the JWT token on the client side (swift app)?
  • Create a Google Cloud Function to generate token then write back to Firebase to use in my API calls?
Frank van Puffelen
  • 499,950
  • 69
  • 739
  • 734
Roggie
  • 986
  • 1
  • 12
  • 34
  • There's probably a misconception. An authentication provider may return a JWT to your app as part of the authentication. In OIDC for example, this is a signed JWT, more precisely, the "id_token" containing user info. Your app should "validate" this token, but an app usually does not create such JWTs. Likewise, if you receive an access_token from any token endpoint, this is always an opaque value. In the app you never try to parse or verify an access token, even when this access token is actually a JWT (or JWS) which you can inspect. – CouchDeveloper Feb 03 '21 at 15:36
  • I have to send encrypted username and password to server via URL by leaving the app and keeping the user signed in on browser? Any leads on this... – Mak13 Nov 03 '21 at 07:07

2 Answers2

9

Here's how to make JSON Web Tokens in Swift using Apple's CryptoKit. It uses the default example in https://jwt.io

import CryptoKit

extension Data {
    func urlSafeBase64EncodedString() -> String {
        return base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}

struct Header: Encodable {
    let alg = "HS256"
    let typ = "JWT"
}

struct Payload: Encodable {
    let sub = "1234567890"
    let name = "John Doe"
    let iat = 1516239022
}

let secret = "your-256-bit-secret"
let privateKey = SymmetricKey(data: Data(secret.utf8))

let headerJSONData = try! JSONEncoder().encode(Header())
let headerBase64String = headerJSONData.urlSafeBase64EncodedString()

let payloadJSONData = try! JSONEncoder().encode(Payload())
let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()

let toSign = Data((headerBase64String + "." + payloadBase64String).utf8)

let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
let signatureBase64String = Data(signature).urlSafeBase64EncodedString()

let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
print(token) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Eric
  • 14,555
  • 13
  • 84
  • 130
  • This is a nice example how to create a signed JWT using Apple's new CryptoKit library. We need to be cautious, however: you are using a symmetric key which requires to be shared between the parties. Unless you have a fancy way to securely share secrets, the key probably ends up being in the binary of the app. A mobile app is considered "non-confidential", means anyone can get his hands on the key. And that means: it's not secure ;) – CouchDeveloper Feb 03 '21 at 15:44
  • @CouchDeveloper If you forego authenticating your network requests via a JWT, and if your network requests are confidential, you're actually increasing your attack surface. I'd obfuscate my binary and use JWTs. – Eric Feb 03 '21 at 18:12
  • Access tokens on the client are always opaque - that is, the app does not (need to) know what it is actually - so, access tokens as JWT are basically just a bunch of octets. Not so for any other info that has been sent by the server to the client: in OIDC for example, the "id_token" will be sent as a signed JWT. In this case, the client needs to parse and validate the JWT using a key. For mobile and SPA, you should use an algorithm which uses a public - private key mechanism (RSA, eclyptic curve, etc.). The public key can be safely used in non-confidential clients. – CouchDeveloper Feb 04 '21 at 21:30
  • So, when you receive data from the server, which is considered confidential (an access token for example) you SHALL store it into the key chain (which is very secure). You can trust the server, as long you are using HTTPS and proper TLS. So, what you get from the server should be the info you are after. You cannot have confidential data of any sort in the binary of your app, a symmetric key for example. It is only a matter of whether it's worth to crack the app, to get to the key - eventually. ;) – CouchDeveloper Feb 04 '21 at 21:39
  • If an attacker is capable of reading a secret key you've embedded in the binary, they'd equally be capable to crack your TLS and read the key your app receives from the backend. Once an attacker has access to your app binary, it's game over regardless of what security measures you have in place, whether it's JWT, obfuscation, TLS, etc.. Obviously this is not a reason to forego TLS or JWTs (quite the contrary). In practical terms, I think obfuscating the binary is a good compromise. So far there haven't been reports of binary obfuscation being broken. – Eric Feb 05 '21 at 16:20
  • How is the private key provided? Is it just the contents of the .p8 file? Witch the header and footer line? – inexcitus Jun 28 '21 at 16:48
  • @inexcitus the private key is just a long string. In the example above it's `"your-256-bit-secret"` but it should ideally be longer – Eric Jun 29 '21 at 08:36
  • @Eric if supporting earlier iOS versions and using CommonCrypto, then what out of that library should be used as a replacement for SymmetricKey()? Thanks – Gruntfluffle Jan 10 '22 at 15:05
2

It depends on how you plan to sign your token. Fundamentally, you'll want some sort of secret to sign the payload of the JWT.

Is your secret an API key that the client already has? If so, there's not a lot of harm just generating it client side.

Is your secret a certificate that's super secret and you can't give out to clients? Then you'll probably want to go with your Firebase idea.

It's pretty common to just have the client do the signing via API key in these situations, but your motivations for locking down your API to begin with are the driving force here.

IBM-Swift looks like the most complete JWT library for swift these days should you decide to go client side.

Jsonwebtoken is a very good JS one should you decide to deploy a GC Function.

Both libraries are very straightforward to use.

ZachChilders
  • 395
  • 3
  • 13
  • 1
    Yes the `secret` is something that can't be published so looks like I need to go down the GC Function route. Any GC Functions sample functions you may be able to point me to?, what triggers can be used for this to work? etc.. – Roggie Feb 19 '20 at 00:00
  • Try using an HTTP Function like described here: https://cloud.google.com/functions/docs/writing/http You'll want to inject your secret via environment variable here: https://cloud.google.com/functions/docs/env-var You can the examples in the Jsonwebtoken docs to sign a normal JSON object, which you'll return as your response to the HTTP request you send to the Cloud Function (or pass through FireBase): https://github.com/auth0/node-jsonwebtoken – ZachChilders Feb 19 '20 at 01:36
  • For most use cases, you don't need a third-party library to encode a JWT. Swift's CryptoKit provides all the functionality you need. – Eric Jan 19 '21 at 13:28
  • @Eric what if you have a requirement to support iOS versions earlier than iOS 13,then CryptoKit is not an option? – Gruntfluffle Jan 08 '22 at 15:33
  • @Gruntfluffle no, CryptoKit is only available on iOS 13 and above. On earlier iOS you can use CommonCrypto. See this: https://stackoverflow.com/a/52785143/1072846 – Eric Jan 09 '22 at 17:03